home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Sound / SoundApp / SoundApp.c < prev    next >
Encoding:
Text File  |  1995-04-12  |  170.4 KB  |  4,682 lines  |  [TEXT/MMCC]

  1. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2. /*
  3. Apple Macintosh Developer Technical Support
  4.  
  5. MultiFinder-Aware SoundApp Application
  6.  
  7. SoundApp
  8.  
  9. SoundApp.c - C Source
  10.  
  11. Jim Reekes - Macintosh Developer Technical Support
  12. Copyright © 1989-1994 Apple Computer, Inc.
  13. All rights reserved.
  14.  
  15. Versions:
  16.         1.03                January, 1990
  17.         1.04                Sept, 1990
  18.         1.2                    August, 1994        translated to C
  19.  
  20. Components:
  21.         SoundApp.c            January, 1990        MPW C source code
  22.         SoundUnit.c            January, 1990        MPW C source code
  23.         SoundUnit.h            January, 1990        MPW C source code
  24.         SoundApp.r            January, 1990        MPW Rez source code
  25.         SoundAppSnds.r        January, 1990        MPW Rez source code
  26.         SoundApp.make        January, 1990        MPW build script
  27.  
  28. Formatting was done with FONT = Courier or Monaco, SIZE = 10, TABS = 4
  29.  
  30. Version comments
  31.  
  32. 1.04: This was an update to support the Utilities Unit that MacDTS
  33. developed.  Some of it was routines formally created for SoundApp,
  34. with additional ones coming from other sources.  This helps to reduce
  35. the "destractive" code from the samples and makes them easier to maintain.
  36.  
  37. 1.1:     • This is the "new" SoundApp which adds some new features.
  38.         • Some knowledge of the new Sound Manager is present
  39.           in areas that were work-arounds for old Sound Manager bugs
  40.         • Recording of sounds is now possible with the Sound Input Manager
  41.         • The document window will keep the Record button hidden if
  42.           the Sound Inpute Manager is not available for recording,
  43.           otherwise it will expand the window size to show this button.
  44.         • Supports copy and pasting of sound resources
  45.         • Conversion to MPW 3.2 was established (with some amount of pain)
  46.         • SoundApp now has its own documents
  47.         • Supports launching by open documents from Finder
  48.         • Added color icons for the System 7.0 desktop
  49.         • Added the new Sound Manager error strings
  50.         • DoErrorSound tests for safe beeps
  51.         • Made Apple Legal happy by getting rid of the xxxxCmd
  52.           and changed it to the freqDurationCmd.  Also got rid of
  53.           any other use of the work "xxxx" in a musical context
  54.  
  55. 1.2:    • Translated into C, some clean up along the way
  56.         • Using universal headers
  57.         • Gave up on the DTS utilities code, everything is self-contained
  58.  
  59. Formatting was done with FONT = Courier, SIZE = 10, TABS = 4
  60.  
  61. many thanks to: Bo3b Johnson, Mark Bennett, Andy Shebanow, Keith
  62. Rollin, Chris Derossi, Pete Helme, Darin Adler, and my co-workers that
  63. sat near me while I was making lots of noise testing this application.
  64.  
  65. To the reader,
  66.  
  67. SoundApp.p is a sample application source file for demonstrating
  68. the Sound Manager.  It requires the use of the SoundUnit to handle
  69. all of the sound routines.  This portion of the source code handles the
  70. application’s management of memory, errors, user interface, etc..
  71. The read me notes are here in the source code because people tend to
  72. not read them otherwise.
  73.  
  74. SoundApp is a demonstration of the Sound Manager released in System
  75. 6.0.x.  SoundApp is also a example of how to write a Macintosh
  76. application that performs good user interface and proper error
  77. handling.  Believe it or not, but the Sound Manager portion was the
  78. easiest part for me to write.  It was all the user interface and error
  79. handling that was the most difficult.  One thing became very clear to
  80. me in the course of writing this application.  The following axiom is
  81. one of the few great truths in the universe.
  82.  
  83. If you write a Macintosh application without MacApp, you’re working
  84. too hard.
  85.  
  86. Throughout the development of SoundApp I would run into typical
  87. situations that make programming a Macintosh too hard.  When this
  88. happened I asked myself “what would MacApp do?” and that was followed
  89. by the thought “then why am I writing code that is already out there
  90. for me to use?”  I started out intending on writing a very simple
  91. application that anyone could read, and understand the Sound Manager.
  92. I felt this meant not requiring the person to read Object Oriented
  93. Pascal.  I accomplished part of my goal.  People should be able to
  94. learn how to use the Sound Manager (in its present condition), but it
  95. didn’t turn out to be such a simple application as I had hoped for.
  96.  
  97. I have put a large amount of comments into the code.  This is
  98. something I’m really picky about.  People do not comment their code
  99. enough.  Each procedure has a paragraph that should explain what to
  100. expect that routine to do, and how it goes about doing it.  There are
  101. some bigger issues that I will put into the release notes below.
  102. There are some things that make the Macintosh harder to develop for
  103. than it should be.  Simple things should be simple.  Too many things
  104. on the Mac that should be simple are not.  Maybe someday these things
  105. will be fixed.
  106.  
  107. • GetFontInfo requires a port set to the font in question.  If I
  108. wanted to find the height of the System Font, I had to first set the
  109. current port to the WindowMgr.  I could have used my own window, but
  110. what if I needed the font info before I had a window available?
  111. • The toolbox blows chunks when your heap gets “too low.”  I believe
  112. this magical number is between 32k and zero.  The odds of blowing
  113. chunks increase logarithmically as one approaches 0 free bytes.
  114. • The Dialog Manager is not a free lunch and in fact will cost you
  115. plenty.
  116. • There’s no safe way to determine how much memory opening a
  117. resource file will take away from your heap.
  118. • There’s no way to determine how safe it is opening a resource that
  119. could be shared by other applications, especially on a local volume.
  120. • The Resource Manager doesn’t always set ResErr.
  121. • The Sound Manager returns even less errors.
  122. • The List Manager returns no errors.
  123. • Writing a staggering routine for new windows encompasses a number
  124. of difficulties.  How does one find the height of a window’s title
  125. region before the window is visible?
  126.  
  127. Am I just a complainer?  Do I have a bad attitude?  Probably, but
  128. I’m just trying to point out the above areas make the Mac programming
  129. experience difficult.  These are areas that get developers into
  130. trouble.  These areas need a sign in front of them that says,
  131. “Danger!”  These are areas that developers get into and then write to
  132. MacDTS for help.
  133.  
  134. Notation Conventions
  135. --------------------
  136. All global variables begin with a lower case “g”.
  137. All constants begin with “k” except for those noted here.
  138. Resource IDs begin with “r”.
  139. Menu IDs being with “m” and items with an “i”.
  140. Resource strings begin with “s”.
  141.  
  142. Human Interface
  143. ---------------
  144. This is the most important and about the most difficult aspect of
  145. programming on the Macintosh.  In the development of SoundApp I gave much
  146. thought to the human interface issues.  In fact, in talking with the Human
  147. Interface Group additions to the existing guidelines were made.  The
  148. method of window stacking used here was a new one.  This was documented in
  149. a Human Interface Tech Note.  I even made one compromise (hard to
  150. believe!) suggested by the Human Interface Group.  I originally had the
  151. buttons and the list in my choice of font and size.  They felt that
  152. buttons should be in the System font and the list should also be the same.
  153. I liked my font choice better, but the group had a point that I really
  154. couldn’t argue with.  That was, “If there isn’t a compelling reason to
  155. change something standard, then don’t change it.”  Buttons on a Macintosh
  156. typically appear in the System font.  Changing the font, just because I
  157. wanted to, was considered gratuitous.  Standard File is in the System font
  158. and it also contains a list and buttons.  Since my window are very similar
  159. to that dialog, I’m using the System Font.
  160.  
  161. SoundApp is never modal unless an error occurs and I need to show and
  162. alert.  Controls are inactivated for inactive windows.  The default button
  163. is given the proper outline, and this outline disappears when the window
  164. is deactivated.  Keyboard equivalents for the buttons cause the button to
  165. appear as if it had been clicked in.  The check box in the Standard File
  166. dialog remembers the user’s last setting.  The about box is only
  167. semi-modal.  It will allow the user access to switch to another application.
  168. The status window under some circumstances was found to disappear too quickly,
  169. so a built in delay was added.  Windows are centered or stacked according
  170. to the Human Interface Guidelines.  The sound level isn’t adjusted by the
  171. application, and instead the users is informed of the current level and
  172. told how it can be adjusted.
  173.  
  174. The About box
  175. -------------
  176. It’s rad.  Has a color icon, shows the 'vers' resource, goes Moof!™.  It
  177. also demonstrates how to handle a modal window without the Dialog Manager.
  178. This technique can be use for any window, including dialog windows.  The
  179. dialog’s update routine would call UpdtDialog.  The really new point to
  180. notice is this window is modal but ONLY within the application’s layer.
  181. While running under MultiFinder, the user can switch to other
  182. applications.  While the About window is present, it is the only window
  183. belonging to the application that accepts user actions.
  184.  
  185. Memory Management
  186. ----------------
  187. This has to be the most difficult portion of a Mac application to
  188. write.  This along with the user interface.  I spent too many nights
  189. chasing down crashes while running the application under low memory.
  190. I found unpleasant surprises while doing this.  The Sound Manager
  191. doesn’t check for NIL pointers.  OpenResFile may take large portions
  192. of my heap away.  The toolbox seems to need at least 32k of free space
  193. in the heap of my Mac II running color.
  194.  
  195. I wrote a simple grow zone procedure that will dump a reserve memory
  196. block.  This is only considered for use in an emergency.  I never rely
  197. on using it directly.  If the reserve has been released, I will not
  198. continue an operation such as playing a sound or showing the status
  199. window until it is regained.  Grow zones should not be considered a
  200. solution to memory management.  They can be used to augment your
  201. overall memory management scheme.
  202.  
  203. Error Checking
  204. --------------
  205. Lots and lots and lots of it.  I could even do more.  Programmers
  206. need to do more of this.  The Sound Manager will crash when
  207. encountering a NIL pointer.  My application should never crash.  If
  208. you can find a way to crash this application, then I want to hear
  209. about it.  Using a bogus 'snd ' resources doesn’t count and I’ve found
  210. many of those.  Writing proper error checking into the code during
  211. development really helped.  Always handle errors, and pass along the
  212. error.  I will let the first error encountered to be passed all the
  213. way up to the caller and eventually my error dialog will show up for
  214. the user.
  215.  
  216. SetPort Strategy
  217. ----------------
  218. Any routine that needs to use Quickdraw will set the port.  I do not
  219. believe that it should also be responsible for restoring the port back
  220. to what it may have been before the routine was called.  So, you’ll
  221. find there is an absence of the GetPort, SetPort, do my thing, and
  222. then SetPort again.  Instead I SetPort and do my thing.  The Mac often
  223. is setting the port unnecessarily.
  224.  
  225. Strings
  226. -------
  227. All of my strings are resources.  There are no strings that appear
  228. within the code.  This helps memory management and allows me to adjust
  229. the application to international systems without compiling any code.
  230. I could simply use ResEdit, or some other tool, to localize this
  231. application.  I find it is just as easy to run Rez again than
  232. attempting to use ResEdit.  Besides, after editing with ResEdit I want
  233. the source for that and would have to run DeRez which isn’t nearly as
  234. clean as my original source files.
  235.  
  236. Window Stacking
  237. ---------------
  238. I hate applications that will open a new document that covers up an
  239. existing document, unless the new document covers the entire screen.
  240. So, my application’s documents have a small window size.  I wanted to
  241. open new windows that would not cover up older ones.  This is nice for
  242. the user, since they will not have to move windows just to get at
  243. other documents.  ResEdit will stagger new windows off of the
  244. frontmost window but I find that this isn’t the best approach.  It
  245. will still cover up other windows and I also don’t like windows that
  246. will open half way between two monitors.  I wanted a better approach:
  247. one that would always stagger new windows and not cover up older ones.
  248.  
  249. When I want to center a window, I need to know its entire rectangle
  250. size.  The rub is that I cannot determine its size until I show it
  251. because I only know about the window’s boundsRect.  This does not
  252. include the area that contains the title bar.  That’s the strucRgn,
  253. which is an empty region for an invisible window.  I could do what
  254. MacApp does, but if I have to do another thing that MacApp already
  255. does I’ll give up and stick with MacApp.  I ended up writing a routine
  256. that takes a guess at the height of the window’s title bar.  This is
  257. another thing that was harder than it should have been.
  258.  
  259. Dialog Manager (and some of the reasons I don’t like it)
  260. --------------------------------------------------------
  261. My first approach was to use modeless dialogs for document windows,
  262. thinking that I could write an application that would demonstrate how to
  263. deal with them and all of their idiosyncrasies.  Not long into the
  264. development cycle it became obvious to me that I was fighting something
  265. that contained more disadvantages than advantages.  I removed all the
  266. dialog code and only used standard windows and controls the old fashion
  267. way.  In the case of the About window, which is semi-modal, I have a test
  268. that will return TRUE for a window that should be treated as modal.  This
  269. allows my window to be handled by my standard event handlers and I don’t
  270. have to write dialog filters.  There are some things that do not get
  271. handled properly while calling ModalDialog.  ModalDialog ignores disk
  272. insert events.  The activate or update events do not get handled for
  273. background windows.  Using a modeless dialog fails with MultiFinder if
  274. switching takes place while the dialog is the frontmost window.  The
  275. problem is that DialogSelect ignores and removes the suspend/resume event.
  276. Another advantage to all this is that drawing was much faster.
  277.  
  278. As an example of some of the problems with ModalDialog and the activate
  279. event.  Try this with the Finder.  Open a window and choose “View by name.”
  280. Then select a few names with the shift key and resize the window so the
  281. vertical scroll bar is visible.  Move this window to one edge of the
  282. screen or a second monitor.  Now choose “Set Startup.”  This is a modal
  283. dialog.  If you look at the Finder window with the selected files, you’ll
  284. notice that the scrollbar and the text are still highlited.  This is not
  285. the proper user interface.  This is because the deactivate routines are
  286. not called while in ModalDialog.  You can even find this problem with
  287. SoundApp.  On deactivate events I will change my controls to the inactive
  288. state.  If you place the buttons to the side of the screen and then bring
  289. up the standard file dialog, you’ll notice that the buttons don’t change
  290. properly.  ModalDialog also prevents the application from updating
  291. background windows too.  To solve this a dialog filter procedure is
  292. required.  In most cases, this filter would be as complex and the event
  293. loop.  It would also make it necessary to call your event routines from
  294. outside of the normal event loop.  All on this isn’t worth the effort.
  295.  
  296. You can see how this does not happen while using this application’s
  297. About window.  Select an item in the document window and choose “Play
  298. Melody.”  This will leave the status window on screen so that you can
  299. drag it to cover the document window.  Now select “About SoundApp” to
  300. bring up the about window.  This causes the status window to close,
  301. which uncovers the document window leaving an invalid area.  The
  302. document window gets an activate event, then the About window appears.
  303. Then the document window is properly deactivated and updated.  Yeah,
  304. just like it should happen.
  305.  
  306. So, the tradeoff was that I didn’t have to work around all the
  307. strange things the Dialog Manager does such as running a secondary
  308. event loop, and requiring me to have userItems or filterProcs.  This
  309. made the code smaller, more readable, and faster.  I think I will
  310. avoid the Dialog Manger from now one unless I’m using a very simple
  311. dialog.  The about window of this application proved too much for the
  312. Dialog Manager.
  313.  
  314. One thing dialogs are good for is running ResEdit and laying out the
  315. dialog.  To help position controls, I used a DLOG resource of the same
  316. size as my WIND resource.  The DITL of this dialog contains the
  317. positions I wanted for my CNTL resources.  This helped me to look at
  318. where I could expect my buttons to show up.  This is one of the main
  319. reasons people think they need the Dialog Manager, because ResEdit
  320. makes it easy to build dialogs.  ResEdit alone has contributed to
  321. nearly all of the Dialog Manager abuse in the world today.
  322.  
  323. I used a Rect resource for positioning the list rectangle of the
  324. document windows.  These windows look very much like a modeless
  325. dialog.  (They used to be, but that presented to many problems.)  The
  326. About window is also a standard window, but shown modally.  Just like
  327. ModalDialog, but my modal window does allow switching under
  328. MultiFinder.  You can change the window to a dBoxProc and then
  329. MultiFinder will not switch while this is the active window.  To help
  330. with the layout of the about window, I position the text within it
  331. based on the size of the window.  The status window does this too.
  332. These two things, the Rect resource and text based on the size of the
  333. window, help when changing the text.  If the new text doesn’t fit,
  334. then resize the window’s resource.  I used some trick with Rez to help
  335. layout my window contents.  Refer to the SoundApp.r sources.
  336.  
  337. I’ve read and understood Tech Note 203, and have learned how to
  338. apply it.  Bo3b Johnson is a smart guy, and developers should trust
  339. his opinions.
  340.  
  341. List Manager
  342. ------------
  343. It’s very easy to be tempted by this part of the toolbox, along with
  344. the Dialog Manager.  The List Manager is a slow beast at times.  It
  345. also has some problems with “doing the right thing.”  I’ve found that
  346. the list will not be updated properly when the user clicks in a cell
  347. that is out of bounds.  LClick will return TRUE with a cell that
  348. doesn’t exists.  LActivate will erase the scrollbars instead of
  349. highlighting the properly.  Finally, the List Manager does not return
  350. errors.  How would a person know if LSetCell worked?
  351.  
  352. I’ve read and understood Tech Note 203, and have learned how to
  353. apply it.
  354.  
  355. Resource Manager
  356. ----------------
  357. I test all the handles being returned from the Resource Manager
  358. before using them, and if I get a NIL then I look at ResError.
  359. ResError sometimes lies and returns noErr and a NIL handle.  ResError
  360. is usually good for getting an error code AFTER you’ve already found
  361. an error.
  362.  
  363. Opening a resource file that is already open by another application
  364. is dangerous.  The Resource Manager will not tell you when you’ve done
  365. this.  There needs to be a OpenRFPerm that will return permission
  366. errors such as resFileBusyErr.  Refer to Tech Note 185.
  367.  
  368. When I or the Toolbox needs to get at one of my resources,
  369. CurResFile must be set to my application.  Also, look out for one
  370. particularly nasty situation when switching resource files.   If the
  371. segment loader goes for a CODE segment, it better be from our resource
  372. file!  The idea here is, in case you didn’t get it already, always
  373. have the current resource file be set to the application.  If a
  374. resource is needed from another file, switch momentarily to get the
  375. resource and immediately restore the current resource file to the
  376. application.  I take an added measure of defense and whenever I need a
  377. resource I use the Get1Resource calls.  These will only search the
  378. current resource file.
  379.  
  380. Strategies For Sound
  381. --------------------
  382. All of the Sound Manager code is contained in the SoundUnit.p.  This
  383. code was written to be general purpose, providing useful routines for
  384. other applications.  Lots of error checking is performed.  I’ve also
  385. extended the support for SndPlay and made it really asynchronous.
  386. I’ve demonstrated most of the abilities the present Sound Manager has
  387. to offer.  I will have to revise the SoundAppUnit to include any new
  388. features (e.g., multi channel support) when the next Sound Manager is
  389. released.
  390.  
  391. I allocate my own memory to be used as sound channels.  I allocate
  392. these pointers early in the application’s startup time to avoid memory
  393. fragmentation.  These channels are of the standard size (holding 128
  394. commands) but I’ve extended the structure to include my own
  395. information.  When I create a new sound channel, I pass it a pointer
  396. to this memory.  This will link in the 'snth' resource and hardware to
  397. my channel.  When I dispose of the channel, the Sound Manager will
  398. purge this resource and disconnect me from the hardware.  When adding
  399. the 'snth' resource, the Sound Manager will allocate a pointer into
  400. the application’s heap instead of the system’s.  This is a modifier
  401. stub used by the 'snth'.  This could cause some problems with memory
  402. management.  I create and dispose of all my channels as soon as
  403. possible, and this doesn’t cause me problems.
  404.  
  405. I keep track of which document is playing a sound, along with a
  406. global of when the application is playing sound.  I needed to keep
  407. track of which document is playing because if the user disposes of
  408. that document, I will have to stop playing the sound contained in it
  409. since the user wants to dispose of that data.  I keep track of when
  410. the application is playing sound in a global.  This is only used by
  411. the routine that calculates the sleep time for WaitNextEvent.
  412.  
  413. I came up with a pretty sick music notational system using Rez.
  414. Refer to the notes in the SoundAppSnds.r file.  If you’ve just
  415. finished a meal, wait four hours before reading.
  416.  
  417. The SoundUnit handles all of the Sound Manager code entirely.  This
  418. eliminates any and all references to the Sound Manager from the
  419. application.  The SoundUnit will return any error encountered while
  420. calling the Sound Manager, and does some extra error checking the
  421. Sound Manager doesn’t do.
  422.  
  423. The portion of the application that uses the wave table synthesizer
  424. is more complex than the other two.  I wanted to include an example
  425. channel modifier for use in the wave table channels.  This would have
  426. been a transpositional modifier that would take a given freqDurationCmd and
  427. transpose it by some amount.  This would be nice for the routine that
  428. plays a scale, by allowing the other three channels to be playing the
  429. same scale but at a different interval.  Unfortunately, I found that
  430. the Sound Manager has bugs using a modifier, at least with the wave
  431. table synths, and could not use them.
  432.  
  433. I’ve created a few wave table sounds and keep them in a 'snd '
  434. resource.  This allows me to change the sound of the wave table
  435. channels and not change any of the code.  Creating wave table data is
  436. complicated.  The example sounds I’ve included are samples I’ve taken
  437. from various sources.  I’ve cleaned them up quit a bit.  This was to
  438. set loop points, try and reduce clicks, correct the sample rates, and
  439. base frequencies.  This is also a complicated task.  Maybe I should document
  440. these techniques.
  441.  
  442. Jim Reekes E.O., Macintosh Developer Technical Support
  443. Sunday, August 7, 1994 7:06:41 PM
  444. */
  445. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  446. // global defines
  447.  
  448. // All of this code is for System 7 and later
  449. #define SystemSevenOrLater 1
  450.  
  451. // I don't use any obsolete stuff in the interfaces, and you shouldn't either.
  452. // You also need to worry about names because the Code Fragment Manager binds
  453. // system calls by name. Read the comments in ConditionalMacros.h
  454. #define OLDROUTINENAMES 0
  455.  
  456.  
  457. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  458. // includes
  459. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  460.  
  461. #include <AppleEvents.h>
  462. #include <Controls.h>
  463. #include <CursorCtl.h>
  464. #include <Desk.h>
  465. #include <Devices.h>
  466. #include <DiskInit.h>
  467. #include <Dialogs.h>
  468. #include <Errors.h>
  469. #include <Events.h>
  470. #include <Files.h>
  471. #include <Icons.h>
  472. #include <GestaltEqu.h>
  473. #include <Lists.h>
  474. #include <LowMem.h>
  475. #include <Menus.h>
  476. #include <Memory.h>
  477. #include <QuickDraw.h>
  478. #include <Resources.h>
  479. #include <Scrap.h>
  480. #include <Script.h>
  481. #include <SegLoad.h>
  482. #include <StandardFile.h>
  483. #include <TextUtils.h>
  484. #include <ToolUtils.h>
  485. #include <Traps.h>
  486. #include <Types.h>
  487. #include <Windows.h>
  488.  
  489. #include <limits.h>
  490. #include <string.h>
  491.  
  492. #include "Sound.h"
  493. #include "SoundInput.h"
  494. #include "SoundUnit.h"
  495.  
  496. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  497. // constants
  498. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  499.  
  500. enum {
  501.     kOSTrapBit                = (1<<11),    /* bit 11 is the OS Trap bit */
  502.     kTrapNumberMask            = 0x07FF,    /* bits used by the A-Trap mechanism */
  503.  
  504.     kPollingSleepTime        = 60,        //MultiFinder’s sleep while sound is playing
  505.  
  506.     kNumberOfMasters        = 3,        //number of master pointer blocks we expect
  507.     kSizeOfReserve            = 32 * 1024L, //size of reserve memory for grow zone proc
  508.     kMinSpace                = 32 * 1024L, //minimum available memory I allow in heap
  509.     kMemForSndDoc            = 20 * 1024L, //minimal amount of memory needed by document
  510.  
  511.     rAppSignature            = 'SAPP',    //applicaiton’s OS signature
  512.     rSndAppDocType            = 'sDoc',    //document's file type
  513.  
  514.     kScrollbarAdjust        = 16-1,        //the width of the scrollbar in the list
  515.     kListFrameInset            = -1,        //inset rectangle adjustment for list frame
  516.     kStadardWhiteSpacing    = 13,        //inset rectangle adjustment for dialog items
  517.  
  518.     kRecordTop                = 40,        //40,50 is topLeft for SndRecord dialog
  519.     kRecordLeft                = 50,        //40,50 is topLeft for SndRecord dialog
  520.  
  521.     kHiliteControlSelect    = 1,        //select the control
  522.     kHiliteControlDeselect    = 0,        //deselect the control
  523.     kCntlOn                    = 1,        //control’s value when truned on
  524.     kCntlOff                = 0,        //control’s value when truned off
  525.  
  526.     kWindowPosStartPt        = 2,        //offset from the topLeft of the screen
  527.     kWindowPosStaggerH        = 16,        //staggering amounts for new windows
  528.     kWindowPosStaggerV        = 3,        //not including the window’s title height
  529.  
  530.     kMinWindowTitleHeight    = 19,
  531.     kButtonFrameSize        = 3,        //button’s frame pen size
  532.     kButtonFrameInset        = -4,        //inset rectangle adjustment around button
  533.     kButtonSizeH            = 17,        //standard height of buttons
  534.     kDafaultButSizeH        = kButtonSizeH - kButtonFrameInset - kButtonFrameInset
  535. };
  536.  
  537. //refer to the SoundApp.r file for the explaination about these numbers
  538. enum {
  539.     kNumOfButtons            = 6,        //the number of buttons in the window
  540.     kSndButtonSizeW            = 100,        //SizeW of buttons in document window
  541.     kSndButtonSizeH            = 22,        //heigth of buttons in document window
  542.     kSoundWindowSizeH        = ((kNumOfButtons * (kStadardWhiteSpacing + kSndButtonSizeH))
  543.                                 + kStadardWhiteSpacing),
  544.     kSoundWindowSizeW        = 296,
  545.  
  546.     //standard position for icons in alerts
  547.     kIconWidthOrHeight        = 32,
  548.     kStdAlertIconTop        = kStadardWhiteSpacing,
  549.     kStdAlertIconLeft        = 23,
  550.     kStdAlertIconBottom        = kStdAlertIconTop + kIconWidthOrHeight,
  551.     kStdAlertIconRight        = kStdAlertIconLeft + kIconWidthOrHeight,
  552.  
  553.     kFSAsynch                = true,        //asynchronous File Manager call
  554.  
  555.     kEnterKey                = '\3',     //the keys I’m looking for
  556. #ifdef applec
  557.     kReturnKey                = '\n',        //MPW C is wrong, the return key is "\r" not "\n"
  558. #else
  559.     kReturnKey                = '\r',
  560. #endif
  561.     kEscape                    = '\33',
  562.     kUpArrow                = '\36',
  563.     kDownArrow                = '\37',
  564.     kPeriod                    = '.',
  565.     kBackspace                = '\b',
  566.  
  567. //This bit set in the ioFlAttrib field if the file’s resource fork is open.
  568.     kResForkOpenBit            = (1 << 2),
  569.  
  570. //For the delay time when flashing the menubar and highlighting a button.
  571.     kDelayTime                = 8,        //8/60ths of a second
  572.  
  573. //The minimal number of ticks the ShowWindow needs to be visible.
  574.     kShowTimeDelay            = 20,
  575.  
  576. //BUG NOTE: timbreCmd value of 255 on the Mac Plus/SE will cause a crash Sound Manager 1
  577.     kSquareWave                = 240,         //square wave for squareWaveSynth
  578.     kSineWave                = 0,        //sine wave for the squareWaveSynth
  579.     kPreferredTimbre        = 190,        //my preferred timbre for the squareWaveSynth
  580.  
  581. //Application snd resources must be higher than this number.
  582.     kSystemSndRange            = 8191        //This is the highest snd id reserved by Apple.
  583. };
  584.  
  585. //The following constants are the resource IDs.
  586. enum {
  587.     rMenuBar                = 1000,        //application’s menu bar
  588.  
  589.     rExitAlert                = 1000,        //emergency exit user alert
  590.     rUserAlert                = 1001,        //error message user alert
  591.     rSoundVolAlert            = 1002,        //sound is set low alert
  592.     rSaveAlert                = 1003,        //save changes? dialog
  593.  
  594.     rGetNameDLOG            = 2000,        //get a name for the sound dialog
  595.     rNameItem                = 3,        //edit text item in rGetNameDLOG
  596.     rUserItem                = 5,        //user item to help draw default outline
  597.  
  598.     rCustomGetFileDLOG        = 2001,        //dialog template for CustomGetFile
  599.     rSndOnlyCheckBox        = 10        //dialog item number in CustomGetFile
  600. };
  601.  
  602. /*
  603. These are the window IDs used in the applications.  Each one must be unique,
  604. since they are used to identify which window the event took place in.
  605. */
  606. enum{
  607.     rAboutWindow            = 1000,        //about window
  608.     rStatusWindow            = 1001,        //sound status window
  609.     rSoundWindow            = 1002,        //sound document window
  610.  
  611.     rListRectID                = 1000,        //resource containing size of list rectangle
  612.  
  613.     rCancelCntl                = 1000,        //stop button ID for the status window
  614.     rPlaySndCntl            = 1001,        //sound button IDs for the sound document window
  615.     rHyperPlayCntl            = 1002,
  616.     rPlayScaleCntl            = 1003,
  617.     rMelodyCntl                = 1004,
  618.     rStopCntl                = 1005,
  619.     rRecordCntl                = 1006,
  620.     rAboutOkCntl            = 1007,
  621.     rUntitled                = 1000,        //string ID for untitled resources
  622.     rAboutText                = 1001,        //string ID for text appearing in about window
  623.     rPutFileMsg                = 1002,        //string for text appearing in SFPutFile dialog
  624.     rMoofIcon                = 1000,        //resource ID for ICON of application
  625.     rAppPict                = 1000,        //resource ID of picture shown in about window
  626.     rSndCursor                = 1000        //cursor resource for our documents
  627. };
  628.  
  629. /*
  630. The following are the snd resource IDs contained in the application,
  631. which start at ID 9000.  IDs 0 - 8191 are reserved for Apple.
  632. */
  633. enum {
  634.     rMoofSound                = 9000,        //snd for the about window
  635.     rScaleSnd                = 9001,        //snd containing a scale
  636.     rMelodyPart1            = 9002,        //snd containing a melody
  637.     rMelodyPart2            = 9003,        //snd containing the harmony
  638.     rMelodyPart3            = 9004,        //snd containing the harmony
  639.     rMelodyPart4            = 9005,        //snd containing the harmony
  640.     rWaveHarmony            = 9006,        //snd containing waveTable for harmony
  641.     rWaveMelody                = 9007,        //snd containing waveTable for melody
  642.     rCounterPt1                = 9008,        //snd containing soprano part of counter point
  643.     rCounterPt2                = 9009,        //snd containing alto part of counter point
  644.     rCounterPt3                = 9010,        //snd containing tenor part of counter point
  645.     rCounterPt4                = 9011,        //snd containing bass part of counter point
  646.     rSopranoVox                = 9012,        //snd containing waveTable of soprano voice
  647.     rAltoVox                = 9013,        //snd containing waveTable of alto voice
  648.     rTenorVox                = 9014,        //snd containing waveTable of tenor voice
  649.     rBassVox                = 9015,        //snd containing waveTable of bass voice
  650.  
  651.     rSampleHarmony            = 9016,        //snd containing the sampled sound harmony
  652.     rSampleMelody            = 9017        //snd containing the sampled sound melody
  653. };
  654.  
  655. //The following are resource IDs for messages.
  656. enum {
  657.     sErrStrings                = 1000,        //error string STR# ID
  658.     sStandardErr            = 1,        //An error has occurred.
  659.     sMemErr                    = 2,        //A Memory Manager error has occurred.
  660.     sResErr                    = 3,        //A Resource Manager error has occurred.
  661.     sCurInUseErr            = 4,        //That file is currently in use.
  662.     sWavesBroken            = 5,        //The wave table synthesizer is not available.
  663.     sWrongVersion            = 6,        //This system does not support the Sound Manager...
  664.     sLowMemory                = 7,        //Memory is too low to continue...
  665.     sNoMenus                = 8,        //Could not find application’s menu resources.
  666.     sInitSoundErr            = 9,        //Could not initialize the SoundUnit.
  667.     sSoundErr                = 10,        //The Sound Manager has encountered an error.
  668.     sNewDocErr                = 11,        //Could not create a new document.
  669.     sInitStatusErr            = 12,        //Error initializing the status window.
  670.     sEditErr                = 13,        //Could not complete the edit command.
  671.     sDocErr                    = 14,        //There is a problem with this document.
  672.  
  673.     sMsgStrings                = 1001,        //message string STR# ID
  674.     sPlayingMsg                = 1,        //playing a sound
  675.     sHyperMsg                = 2,        //playing a sound the Hyper way
  676.     sScaleMsg                = 3,        //playing scale
  677.     sMelodyMsg                = 4,        //playing melody
  678.     sTimbresMsg                = 5,        //playing various timbres
  679.     sCounterPtMsg            = 6,        //playing 4 part counter point
  680.  
  681.     sSMErrStrings            = 1002        //strings describing Sound Manager errors
  682. };
  683.  
  684. /*
  685. The following constants are used to identify menus and items. The menu IDs
  686. have an “m” prefix and the item numbers within each menu have an “i” prefix.
  687. */
  688.  
  689. enum {
  690.     mApple                    = 128,        //Apple menu and items
  691.     iAbout                    = 1,
  692.  
  693.     mFile                    = 129,        //File menu and items
  694.     iNew                    = 1,
  695.     iOpen                    = 2,
  696.     iClose                    = 4,
  697.     iQuit                    = 12,
  698.  
  699.     mEdit                    = 130,        //Edit menu and items
  700.     iUndo                    = 1,
  701.     iCut                    = 3,
  702.     iCopy                    = 4,
  703.     iPaste                    = 5,
  704.     iClear                    = 6,
  705.  
  706.     mDemos                    = 1000,        //Demos menu and items
  707.     iCheckVolume            = 1,
  708.     iSquareScale            = 3,
  709.     iSquareMelody            = 4,
  710.     iSquareTimbre            = 5,
  711.     iWaveScale                = 7,
  712.     iWaveMelody                = 8,
  713.     iWaveSATB                = 9,
  714.     iSampleMelody            = 11,
  715.     iSampleSATB                = 12
  716. };
  717.  
  718.  
  719. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  720. // macros
  721. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  722.  
  723. // Define HighWord and LowWord macros for efficiency.
  724. #define HighWord(aLong)        ((aLong) >> 16)
  725. #define LowWord(aLong)        ((aLong) & 0x0000FFFF)
  726.  
  727. // accessing a rectangle's points
  728. #define TopLeft(r)            (* (Point *) &(r).top)
  729. #define BottomRight(r)        (* (Point *) &(r).bottom)
  730.  
  731. #define AbsoluteValue(n)    ((n > 0) ? n : -n)
  732.  
  733. #if GENERATINGCFM
  734. #define CreateRoutineDescriptor(info, proc)                                    \
  735.  RoutineDescriptor g##proc##RD = BUILD_ROUTINE_DESCRIPTOR(info, proc)
  736.  
  737. #define GetRoutineAddress(proc)    (&g##proc##RD)
  738.  
  739. #else
  740. #define GetRoutineAddress(proc)    proc
  741. #endif
  742.  
  743. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  744. // types
  745. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  746.  
  747. typedef RectPtr *RectHandle;
  748.  
  749. /*
  750. This is a document layout that contains the window record and references
  751. to the associating data.  The window record MUST be the first field.  This
  752. is because I use the window pointer returned by the Toolbox to be a
  753. pointer to my document.  To confirm that the window pointer is a document
  754. pointer, I store an application reference in the window record’s refCon.
  755. Then, I use a routine to test for the presence of this reference to insure
  756. I’m looking at one of my document’s windows.
  757. */
  758.  
  759. struct SndDocument {
  760.     WindowRecord    window;                // must be first field
  761.     short             resFile;            // document’s resource file
  762.     short             vRefNum;
  763.     long             dirID;
  764.     ListHandle        list;                // document’s list of sounds
  765.     Boolean            sndInUse;            // document is using a 'snd ' resource
  766. };
  767. typedef struct SndDocument SndDocument;
  768. typedef SndDocument *SndDocPeek;        // to peek at the document record
  769.  
  770. // This is the status window layout.  The concept here is similar to the
  771. // document type mentioned above.  The message is a string handle used to
  772. // store the current message.
  773.  
  774. struct StatusWindow {
  775.     WindowRecord    window;
  776.     StringHandle    message;            // current text of status message
  777.     long            showTime;            // time window was shown
  778. };
  779. typedef struct StatusWindow StatusWindow;
  780. typedef StatusWindow *StatWindowPeek;
  781.  
  782. // This is the about window layout.  The concept here is similar to the
  783. // document type mentioned above.  The comment is a string handle used to
  784. // store the current message.
  785.  
  786. struct AboutWindow {
  787.     WindowRecord    window;
  788.     Handle            appPict;            // handle to picture of app’s name
  789.     Handle             comment;            // handle to string of about comments
  790. };
  791. typedef struct AboutWindow AboutWindow;
  792. typedef AboutWindow *AboutWPeek;
  793.  
  794. // This is the template to the WIND resource.  I used it to load in the WIND
  795. // resource and then adjust the boundsRect.  I also look at the procID to
  796. // determine if it has a title bar or drag region.
  797.  
  798. struct WindowTemplate {                    // template to a WIND resource
  799.     Rect        boundsRect;
  800.     short        procID;
  801.     Boolean        visible;
  802.     Boolean        filler1;
  803.     Boolean        goAwayFlag;
  804.     Boolean        filler2;
  805.     long        refCon;
  806.     Str255        title;
  807. };
  808. typedef struct WindowTemplate WindowTemplate;
  809. typedef WindowTemplate *WindowTPtr, **WindowTHndl;
  810.  
  811. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  812. // inlines
  813. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  814.  
  815. /*
  816. This is a handy routine to copy pascal strings. It's smaller than the
  817. library call pstrcopy, and faster too.
  818. */
  819.  
  820. #if GENERATING68K
  821. #pragma parameter PStringCopy(__A0,__A1)
  822. void PStringCopy(StringPtr source, StringPtr destination)
  823.  FIVEWORDINLINE (0x7000, 0x1010, 0x12D8, 0x51C8, 0xFFFC);
  824. //                    moveq    #0,d0
  825. //                    move.b    (a0),d0
  826. //            loop    move.b    (a0)+,(a1)+
  827. //                    dbra    d0,loop
  828. #else
  829. #define PStringCopy(source, dest) strncpy((char *)dest, (char *)source, StrLength(source) + 1)
  830. #endif
  831.  
  832. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  833. // prototypes
  834. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  835.  
  836. short NumToolboxTraps(void);
  837. Boolean TrapExists(short theTrap);
  838. pascal long MyGrowZone(Size cbNeeded);
  839. Boolean LowOnReserve(void);
  840. void RecoverReserve(void);
  841. Boolean AllocateReserve(void);
  842. Boolean FailLowMemory(long memRequest);
  843. Boolean HasSelection(SndDocPeek sndDoc);
  844. OSErr GetSelection(SndDocPeek sndDoc, SndListHandle *sndHandle);
  845. void SelectNextCell(ListHandle list, Boolean next);
  846. void SelectSndCell(SndDocPeek sndDoc, short resID);
  847. OSErr InitSndList(SndDocPeek docPtr);
  848. Boolean OpenByApp(FSSpecPtr file, WindowPtr *window);
  849. Point GetGlobalMouse(void);
  850. short GetWTitleHeight(short variant);
  851. Point GetGlobalTopLeft(WindowPtr window);
  852. void MyParamText(StringHandle text, StringPtr cite0, StringPtr cite1);
  853. void CenterWindowRect(short variant, Rect *theRect);
  854. WindowPtr GetCenteredWindow(short id, Ptr p, WindowPtr behind);
  855. short CenteredAlert(short alertID);
  856. DialogPtr GetCenteredDialog(short id, Ptr p, WindowPtr behind);
  857. void DoButtonOutline(ControlHandle button);
  858. void SelectButton(ControlHandle button);
  859. void ActivateSndCntls(SndDocPeek sndDoc);
  860. Boolean IsDAWindow(WindowPtr window);
  861. Boolean IsDocWindow(WindowPtr window);
  862. Boolean IsModalWindow(WindowPtr window);
  863. void KillSound(void);
  864. Boolean DoCloseWindow(WindowPtr window);
  865. void AlertUser(short error, short messageID);
  866. void EmergencyExit(short message);
  867. void Terminate(void);
  868. void DrawStatusWindow(void);
  869. void ShowStatusWindow(short messageID);
  870. void InitStatusWindow(void);
  871. pascal void DoErrorSound(short soundNo);
  872. void CheckSoundVolume(void);
  873. void AddSndDocControls(WindowPtr window);
  874. Boolean PositionAvailable(Point newPt, short wTitleHeight);
  875. WindowPtr NewStackedWindow(short windID, Ptr windStorage);
  876. OSErr CreateSoundDoc(short resRef, FSSpecPtr file);
  877. void OpenSoundDoc(FSSpecPtr file);
  878. void NewSoundDoc(void);
  879. pascal Boolean SFFilter(CInfoPBPtr p, Boolean *sndFilesOnly);
  880. pascal short SFGetHook(short mySFItem, DialogPtr dialog, void *sndFilesOnly);
  881. void GetSoundDoc(void);
  882. void DrawAboutWindow(WindowPtr window);
  883. void DoAbout(void);
  884. void PlaySelectedSnd(SndDocPeek sndDoc, short message);
  885. void PlaySndSong(SndDocPeek sndDoc, short sndID);
  886. void PlaySquareSong(short sndID);
  887. void PlaySquareTimbres(void);
  888. void PlayWaveScale(void);
  889. OSErr GetSndSongs(short sndSongID1, short sndSongID2, short sndSongID3, short sndSongID4, SndListHandle *sndSong1, SndListHandle *sndSong2, SndListHandle *sndSong3, SndListHandle *sndSong4);
  890. OSErr InstallWaveSnds(SndChannelPtr chan1, SndChannelPtr chan2, SndChannelPtr chan3, SndChannelPtr chan4, short waveID1, short waveID2, short waveID3, short waveID4);
  891. void PlayWaveMelody(void);
  892. void PlayWaveSATB(void);
  893. void PlaySampleMelody(void);
  894. void PlaySampleSATB(void);
  895. pascal void DefaultOutline(WindowPtr window, short theItem);
  896. void GetSndName(Str255 sndName);
  897. OSErr AddSnd(SndDocPeek sndDoc, StringPtr sndNamePtr, SndListHandle sndHndl);
  898. void ClearSnd(SndDocPeek sndDoc);
  899. void DoRecordSound(SndDocPeek sndDoc);
  900. void AdjustMenus(void);
  901. void CopySnd(SndDocPeek sndDoc);
  902. void CutSnd(SndDocPeek sndDoc);
  903. void PasteSnd(SndDocPeek sndDoc);
  904. void DoMenuCommand(long menuResult);
  905. void DrawSndWindow(WindowPtr window);
  906. void DoKeyDown(char key, WindowPtr window);
  907. Boolean ListClick(SndDocPeek sndDoc, EventRecord *event);
  908. void DoSndDocClick(SndDocPeek sndDoc, EventRecord *event);
  909. void DoStatClick(StatWindowPeek statWindow, EventRecord *event);
  910. void DoAboutClick(WindowPtr window, EventRecord *event);
  911. void DoUpdate(WindowPtr window);
  912. void DoActivate(WindowPtr window, Boolean becomingActive);
  913. pascal OSErr QuitApplicationEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
  914. pascal OSErr OpenDocumentsEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
  915. pascal OSErr PrintDocumentsEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
  916. pascal OSErr OpenApplicationEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon);
  917. void DoEvent(EventRecord *event);
  918. void AdjustCursor(RgnHandle region);
  919. void EventLoop(void);
  920. void Initialize(void);
  921. void main(void);
  922.  
  923. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  924. // Globals (The “g” prefix is used to emphasize that a variable is global.)
  925. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  926.  
  927. /*
  928. gMac is used to hold the result of a SysEnvirons call. This makes it
  929. convenient for any routine to check the environment. It is considered
  930. global information, anyway.
  931. */
  932.  
  933.     SysEnvRec gMac;                    //set up by Initialize
  934.  
  935. /*
  936. gReserveMemory is a handle of reserve memory used by my grow zone
  937. procedure.  If memory is attempted to be allocated and fails, my grow
  938. zone will release this reserved block of memory.
  939. */
  940.  
  941.     Handle gReserveMemory;
  942.  
  943. /*
  944. gStatusWindow is the window that tells the user what’s happening.  I show
  945. it while a sound is playing, and remove it as soon as the sound is
  946. complete.  It is initialized once during startup, and from then on two
  947. routines are used to show and hide it.  This demonstrates orchestrating
  948. multimedia, that sound/graphic idea, at least in a crude sort of way.
  949. */
  950.  
  951.     StatWindowPeek gStatusWindow;
  952.  
  953. /*
  954. gAppResRef is the application’s resource file reference.  I need to save
  955. this since I can open other resource files.  The current resource file is
  956. always gAppResRef unless I momentarily set it to another file to read its
  957. resources, and then immediately restore it back.
  958. */
  959.  
  960.     short gAppResRef;                //set up by Initialize
  961.  
  962. /*
  963. gInBackground is maintained by our osEvent handling routines. Any part of
  964. the program can check it to find out if it is currently in the background.
  965. */
  966.  
  967.     Boolean gInBackground;            //maintained by Initialize and DoEvent
  968.  
  969. /*
  970. We have to allocate our own QuickDraw globals when creating code for PowerMacs.
  971. MetroWerks declares "qd" in their runtime, so don't do that twice.
  972. */
  973.  
  974. #if GENERATINGCFM && !defined(__MWERKS__)
  975.     QDGlobals    qd;
  976. #endif
  977.  
  978. /*
  979. This is necessary because of the Apple Event Manager. We used to be able
  980. to quit directly when the user choose Quit from the File menu. But because
  981. of the design of Apple Events, we cannot call ExitToShell from the quit handler.
  982. That event handler must set a flag and then check it in the event loop.
  983. */
  984.  
  985.     Boolean gTerminate;
  986.  
  987.  
  988. // allocate the RoutineDescriptors for Power Mac toolbox calls
  989. #if GENERATINGCFM
  990. CreateRoutineDescriptor(uppGrowZoneProcInfo, MyGrowZone);
  991. CreateRoutineDescriptor(uppFileFilterYDProcInfo, SFFilter);
  992. CreateRoutineDescriptor(uppDlgHookYDProcInfo, SFGetHook);
  993. CreateRoutineDescriptor(uppUserItemProcInfo, DefaultOutline);
  994. CreateRoutineDescriptor(uppUserItemProcInfo, QuitApplicationEvent);
  995. CreateRoutineDescriptor(uppUserItemProcInfo, OpenDocumentsEvent);
  996. CreateRoutineDescriptor(uppUserItemProcInfo, PrintDocumentsEvent);
  997. CreateRoutineDescriptor(uppUserItemProcInfo, OpenApplicationEvent);
  998. #endif
  999.  
  1000. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1001. // IMPLEMENTATION
  1002. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1003.  
  1004. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1005. /*
  1006. InitGraf is always implemented (trap 0xA86E). If the trap table is big
  1007. enough, trap 0xAA6E will always point to either Unimplemented or some other
  1008. trap, but will never be the same as InitGraf. Thus, you can check the size
  1009. of the trap table by asking if the address of trap 0xA86E is the same as 0xAA6E.
  1010. */
  1011.  
  1012. #pragma segment Initialize
  1013. short NumToolboxTraps(void)
  1014. {
  1015.     if ( GetToolboxTrapAddress(_InitGraf) == GetToolboxTrapAddress(_InitGraf + 0x0200) )
  1016.         return (0x0200);
  1017.     else
  1018.         return (0x0400);
  1019. }
  1020.  
  1021. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1022. /*
  1023. Check to see if a given trap is implemented.  GetTrapAddress may screw up and
  1024. wrap around into the trap table if you give it a toolbox trap that is out of
  1025. range, so check for this first.
  1026. */
  1027.  
  1028. #pragma segment Initialize
  1029. Boolean TrapExists(short theTrap)
  1030. {
  1031.     UniversalProcPtr    trapAddress;
  1032.  
  1033.     if ( !(theTrap & kOSTrapBit) )                        // is this an OS trap?
  1034.         trapAddress = GetOSTrapAddress(theTrap);
  1035.     else {                                                // no, this is a tool trap
  1036.         theTrap &= kTrapNumberMask;                        // get the trap number
  1037.         if ( theTrap < NumToolboxTraps() )                // can this tool trap exist?
  1038.             trapAddress = GetToolTrapAddress(theTrap);
  1039.         else
  1040.             return (false);
  1041.     }
  1042.     return ( GetToolboxTrapAddress(_Unimplemented) != trapAddress );
  1043. }
  1044.  
  1045. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1046. /*
  1047. This is a very basic grow zone procedure.  My application keeps a reserve
  1048. handle of memory in case the Memory Manager gets a request for some memory
  1049. that is not available in my heap.  If memory were to get tight (<32k),
  1050. the Toolbox will crash the system, especially Quickdraw.  Before releasing
  1051. the reserve handle I make sure it isn’t the GZSaveHnd.  This handle cannot
  1052. be touched by the grow zone procedure.
  1053.  
  1054. WARNING:  The grow zone procedure will be called and A5 may not be valid.
  1055. Read Tech Note #136 and 208
  1056. */
  1057.  
  1058. #pragma segment Main
  1059. pascal long MyGrowZone(Size cbNeeded)
  1060. {
  1061. #pragma unused (cbNeeded)
  1062.     long theA5;
  1063.     long result;
  1064.  
  1065.     theA5= SetCurrentA5();
  1066.     if (((*gReserveMemory) != nil) && (gReserveMemory != GZSaveHnd())) {
  1067.         EmptyHandle(gReserveMemory);
  1068.         result = kSizeOfReserve;                    //released this much memory
  1069.     } else
  1070.         result = 0;                                //this may release more memory
  1071.     theA5= SetA5(theA5);
  1072.     return(result);
  1073. }
  1074.  
  1075. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1076. /*
  1077. Before my application attempts to use more memory, I call this routine
  1078. to check if I’m already using my reserve memory.  If so, then I better
  1079. prepare to die or get my reserve back.
  1080. */
  1081.  
  1082. #pragma segment Main
  1083. Boolean LowOnReserve(void)
  1084. {
  1085.     return((*gReserveMemory) == nil);        // empty handle is low reserve
  1086. }
  1087.  
  1088. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1089. /*
  1090. This is called from the event loop if LowOnReserve returns that I’m out of
  1091. the reserve memory.  This will recover the reserve memory block.  If this
  1092. fails, it will be called the next time through the event loop.
  1093. */
  1094.  
  1095. #pragma segment Main
  1096. void RecoverReserve(void)
  1097. {
  1098.     ReallocateHandle(gReserveMemory, kSizeOfReserve);
  1099. }
  1100.  
  1101. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1102. /*
  1103. This is called at startup time to allocate the reserve memory block used
  1104. in the grow zone procedure.  If I’m unable to obtain this reserve, then
  1105. return false to signal a failure.
  1106. */
  1107.  
  1108. #pragma segment Initialize
  1109. Boolean AllocateReserve(void)
  1110. {
  1111.     gReserveMemory = NewHandle(kSizeOfReserve);
  1112.     return(gReserveMemory != nil);
  1113. }
  1114.  
  1115. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1116. /*
  1117. Call PurgeSpace and see if the requested amount of memory exists in the
  1118. heap including a minimal amount of heap space.  Also, if my grow zone’s
  1119. reserve has been release, that’s considered a failure.  I don’t perform
  1120. any purging here.  The Memory Manager will do this if it needs the space.
  1121. This routine can be called with a memRequest == 0.  This checks if the heap
  1122. space is getting critical.
  1123. */
  1124.  
  1125. #pragma segment Main
  1126. Boolean FailLowMemory(long memRequest)
  1127. {
  1128.     long    total;
  1129.     long    contig;
  1130.  
  1131.     PurgeSpace(&total, &contig);
  1132.     return((total < (memRequest + kMinSpace)) || LowOnReserve());
  1133. }
  1134.  
  1135. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1136. /*
  1137. This a simple test to see if the user has a currently selected list item.
  1138. */
  1139.  
  1140. #pragma segment Main
  1141. Boolean HasSelection(SndDocPeek sndDoc)
  1142. {
  1143.     Cell aCell;
  1144.  
  1145.     SetPt(&aCell, 0, 0);
  1146.     return(LGetSelect(true, &aCell, sndDoc->list));
  1147. }
  1148.  
  1149. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1150. /*
  1151. Given a document, this will find the currently selected cell in the list.
  1152. Then using this cell as an index number, I call Get1IndResource.  This
  1153. will return the proper handle.  Since the List Manager is zero based and
  1154. the Resource Manager isn’t, I have to add one to the index.
  1155.  
  1156. BUG NOTE: GetIndResource will return a bogus handle if the index is not
  1157. positive.
  1158. */
  1159.  
  1160. #pragma segment Main
  1161. OSErr GetSelection(SndDocPeek sndDoc, SndListHandle *sndHandle)
  1162. {
  1163.     Cell    aCell;
  1164.     OSErr    result;
  1165.  
  1166.     result = noErr;
  1167.     SetPt(&aCell, 0, 0);
  1168.     if (LGetSelect(true, &aCell, sndDoc->list)) {
  1169.         if (aCell.v > -1) {                        // GetIndResource doesn’t like < 0
  1170.             UseResFile(sndDoc->resFile);        // only get our resources
  1171.             *sndHandle = (SndListHandle)Get1IndResource(soundListRsrc, aCell.v + 1);
  1172.             if (*sndHandle == nil)
  1173.                 result = ResError();            // return any error
  1174.             UseResFile(gAppResRef);                // restore our resource file
  1175.         }
  1176.     }
  1177.     return(result);
  1178. }
  1179.  
  1180. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1181. /*
  1182. This is used to allow the user to select items from the list by using
  1183. the arrow keys.  Given a list and the direction, this will select the
  1184. next cell.  It will do nothing if we’re at the first cell and the user
  1185. wants to do even higher (sped), or if we’re at the last cell and the
  1186. user wants to go even lower (rent a clue bud).
  1187. */
  1188.  
  1189. #pragma segment Main
  1190. void SelectNextCell(ListHandle list, Boolean next)
  1191. {
  1192.     Cell    aCell;
  1193.     short    lastItem;
  1194.  
  1195.     lastItem = (**list).dataBounds.bottom - 1;        // bounds is 1 greater
  1196.     SetPt(&aCell, 0, 0);
  1197.     if (LGetSelect(true, &aCell, list)) {
  1198.         if ((next && (aCell.v < lastItem)) || ((!next) && (aCell.v > 0))) {
  1199.             LSetSelect(false, aCell, list);
  1200.             if (next)
  1201.                 aCell.v = aCell.v + 1;
  1202.             else
  1203.                 aCell.v = aCell.v - 1;
  1204.             SetPt(&aCell, aCell.h, aCell.v);
  1205.             LSetSelect(true, aCell, list);
  1206.             LAutoScroll(list);
  1207.         }
  1208.     }
  1209.     else                                            // if no cells selected...
  1210.         LSetSelect(true, aCell, list);                // select the first cell
  1211. }
  1212.  
  1213. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1214. /*
  1215. VERSION 1.1:  This routine will go through the list of snd resources
  1216. looking for the one that has the resource ID matching the parameter passed
  1217. in.  First thing to do is deselect the current selection.  Then once the
  1218. matching resource is found, select that one and scroll it into view.  One
  1219. assumption made is that the resources and the list are both index in the
  1220. same order.  This is true for the sndDoc, since I built the list this way.
  1221. */
  1222.  
  1223. #pragma segment Main
  1224. void SelectSndCell(SndDocPeek sndDoc, short resID)
  1225. {
  1226.     Str255 name;
  1227.     Cell aCell;
  1228.     Handle sndHndle;
  1229.     ResType rType;
  1230.     short testID;
  1231.     short index;
  1232.     short numSnd;
  1233.  
  1234.     index = 0;
  1235.     aCell.h = 0;
  1236.     aCell.v = 0;
  1237.     if (LGetSelect(true, &aCell, sndDoc->list))
  1238.         LSetSelect(false, aCell, sndDoc->list); //deselect any cell
  1239.     UseResFile(sndDoc->resFile);                //count only its resources
  1240.     numSnd = Count1Resources(soundListRsrc);    //number of sounds available
  1241.     do {
  1242.         index++;
  1243.         SetResLoad(false);                        //don’t load any resources
  1244.         sndHndle = Get1IndResource(soundListRsrc, index); //only get snd from file
  1245.         SetResLoad(true);                            //back to normal resource operations
  1246.         GetResInfo(sndHndle, &testID, &rType, name);
  1247.     } while ((resID != testID) && (index < numSnd));
  1248.  
  1249.     UseResFile(gAppResRef);                        //restore our resource file
  1250.     aCell.h = 0;
  1251.     aCell.v = index - 1;
  1252.     LSetSelect(true, aCell, sndDoc->list);
  1253.     LAutoScroll(sndDoc->list);
  1254. }
  1255.  
  1256. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1257. /*
  1258. This is used to create the list showing all the snd resources in the file.
  1259. If anything goes wrong while attempting to get any of the resources, then I
  1260. return false.  I don’t want the document to continue if something goes
  1261. wrong while reading a resource.  If I do get the resource, then I add its
  1262. name to the list in a new row.  I’m assured that at least one resource is
  1263. in the file before this routine is called.  Untitled resources will get one.
  1264. */
  1265.  
  1266. #pragma segment Open
  1267. OSErr InitSndList(SndDocPeek docPtr)
  1268. {
  1269.     short            index;
  1270.     short            resID;
  1271.     short            newRow;
  1272.     short            numSnd;
  1273.     Handle            resHandle;
  1274.     StringHandle    strHandle;
  1275.     ResType            itsType;
  1276.     Str255            resName;
  1277.     Str255            untitled;
  1278.     Cell            aCell;
  1279.     OSErr            result = noErr;
  1280.  
  1281.     strHandle = (StringHandle)Get1Resource('STR ', rUntitled);
  1282.     if (strHandle != nil)
  1283.         PStringCopy(*strHandle, untitled);        // save no name title
  1284.     else
  1285.         untitled[0] = 0;                        // at least an empty string
  1286.     UseResFile(docPtr->resFile);
  1287.     numSnd = Count1Resources(soundListRsrc);
  1288.     newRow = LAddRow(numSnd, 0, docPtr->list);
  1289.     for (index = 1; index <= numSnd; index++) {
  1290.         SetResLoad(false);                        // don’t load any resources
  1291.         resHandle = Get1IndResource(soundListRsrc, index); // only get snd from file
  1292.         SetResLoad(true);                        // back to normal operations
  1293.         if (resHandle != nil) {                    // only if I got the snd
  1294.             GetResInfo(resHandle, &resID, &itsType, resName);
  1295.             if (StrLength(resName) == 0)        // if the snd isn’t named...
  1296.                 PStringCopy(untitled, resName);    // give it a name
  1297.             SetPt(&aCell, 0, index - 1);
  1298.             LSetCell(&resName[1], StrLength(resName), aCell, docPtr->list);
  1299.         }
  1300.         else {                                     // resHandle == NIL
  1301.             result = resNotFound;                // problem with resource file
  1302.             break;                                // get out of the loop
  1303.         }
  1304.     }
  1305.     UseResFile(gAppResRef);                        // restore our resource file
  1306.     return(result);
  1307. }
  1308.  
  1309. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1310. /*
  1311. In the case that the user has attempted to open a resource file that is
  1312. already open, this routine will scan my open documents for it.  If I find
  1313. that the requested file is already open by the application, then return
  1314. true and its window pointer.  The application can then simply select that
  1315. window.  If the resource file is open but not by this application, that’s
  1316. a problem and I don’t use the resource file.  Many problems with using an
  1317. open resource file.  Now if the Resource Manager would take care of this
  1318. problem for us...
  1319. */
  1320.  
  1321. #pragma segment Open
  1322. Boolean OpenByApp(FSSpecPtr file, WindowPtr *window)
  1323. {
  1324.     Str255 docName;
  1325.     WindowPtr testWindow;
  1326.     Boolean result;
  1327.  
  1328.     result = false;
  1329.     testWindow = FrontWindow();
  1330.     while (testWindow != nil) {
  1331.         if (GetWRefCon(testWindow) == rSoundWindow)
  1332.             if ((file->vRefNum == ((SndDocPeek)testWindow)->vRefNum)
  1333.                 && (file->parID == ((SndDocPeek)testWindow)->dirID))
  1334.             {
  1335.                 GetWTitle(testWindow, docName);
  1336.                 if (EqualString(file->name, docName, false, true))
  1337.                 {
  1338.                     *window = testWindow;
  1339.                     result = true;
  1340.                     break;
  1341.                 }
  1342.             }
  1343.         testWindow = (WindowPtr)(((WindowPeek)testWindow)->nextWindow);
  1344.     }
  1345.     return(result);
  1346. }
  1347.  
  1348. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1349. /*
  1350. Get the global coordinates of the mouse.  Get the global coordinates by
  1351. calling GetMouse and LocalToGlobal.  This assumes the current port is a
  1352. valid graf port.  When wouldn’t it be?
  1353. */
  1354.  
  1355. #pragma segment Main
  1356. Point GetGlobalMouse(void)
  1357. {
  1358.     Point globalPt;
  1359.  
  1360.     GetMouse(&globalPt);
  1361.     LocalToGlobal(&globalPt);
  1362.     return(globalPt);
  1363. }
  1364.  
  1365. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1366. /*
  1367. Try and determine the window’s title bar height.  This isn’t easy,
  1368. especially if the window is invisible.  We all know how the standard Apple
  1369. System WDEF works, at least at this date we do.  I assume that method as
  1370. shown below.  I could do what MacApp does with the Function CallWDefProc.
  1371. If the user has installed a replacement for the System WDEF, then it’s
  1372. their problem to deal with.  My method will work as long as Apple doesn’t
  1373. change how the WDEF in System 6.0 calculates the title bar height.  This
  1374. will allow my application to work on international Macs that have a larger
  1375. system font than Chicago.  I check the window’s variant code for one that
  1376. includes a title bar.  I will have to change this routine to adjust for
  1377. the modal-moveable window type, which hasn’t been defined yet.
  1378.  
  1379. In this routine, I violate my rule about not using the GetPort, SetPort,
  1380. SetPort sequence mentioned at the start of the file. Mostly, I do this
  1381. because it’s not all that apparent that a routine called GetWTitleHeight
  1382. will change the port, so I make sure that it doesn’t.
  1383. */
  1384.  
  1385. #pragma segment Open
  1386. short GetWTitleHeight(short variant)
  1387. {
  1388.     FontInfo info;
  1389.     GrafPtr curGraf;
  1390.     GrafPtr wMgrPort;
  1391.     short wTitleHeight;
  1392.     short result;
  1393.  
  1394.     if (    (variant == documentProc)
  1395.          || (variant == noGrowDocProc)
  1396.          || (variant == zoomDocProc)
  1397.          || (variant == rDocProc) )
  1398.     {
  1399.         GetPort(&curGraf);
  1400.         GetWMgrPort(&wMgrPort);                        //I need to know the font...
  1401.         SetPort(wMgrPort);                            //info in the System’s port
  1402.         GetFontInfo(&info);
  1403.         SetPort(curGraf);                            //restore current port
  1404.         wTitleHeight = info.ascent + info.descent + info.leading + 2;
  1405.         if (wTitleHeight < kMinWindowTitleHeight)
  1406.             wTitleHeight = kMinWindowTitleHeight;
  1407.         result = wTitleHeight;
  1408.     } else
  1409.         result = 0;                            //other window types have no title
  1410.     return(result);
  1411. }
  1412.  
  1413. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1414. /*
  1415. Given a window, this will return the top left point of the window’s
  1416. port in global coordinates.  Something this doesn’t include, is the
  1417. window’s drag region (or title bar).  This returns the top left point
  1418. of the window’s content area only.
  1419.  
  1420. In this routine, I violate my rule about not using the GetPort, SetPort,
  1421. SetPort sequence mentioned at the start of the file. Mostly, I do this
  1422. because it’s not all that apparent that a routine called GetGlobalTopLeft
  1423. will change the port, so I make sure that it doesn’t.
  1424. */
  1425.  
  1426. #pragma segment Main
  1427. Point GetGlobalTopLeft(WindowPtr window)
  1428. {
  1429.     GrafPtr theGraf;
  1430.     Point globalPt;
  1431.  
  1432.     GetPort(&theGraf);
  1433.     SetPort(window);
  1434.     globalPt = TopLeft(window->portRect);
  1435.     LocalToGlobal(&globalPt);
  1436.     SetPort(theGraf);
  1437.     return(globalPt);
  1438. }
  1439.  
  1440. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1441. /*
  1442. I’m using string pointers (not necessarily Memory Manager pointers)
  1443. to the replacement strings.  Using Str255 would copy the string to the
  1444. stack, and then to the text handle.  This way it is only copied once.
  1445. */
  1446.  
  1447. #pragma segment Main
  1448. void MyParamText(StringHandle text, StringPtr cite0, StringPtr cite1)
  1449. {
  1450.     long    offSet;
  1451.     char    param0[2] = {'^', '0'};
  1452.     char    param1[2] = {'^', '1'};
  1453.     long    newLength;
  1454.  
  1455.     if (StrLength(cite0) > 0)
  1456.         offSet = Munger((Handle)text, 1, param0, sizeof(param0), (Ptr)&(cite0[1]), StrLength(cite0));
  1457.     if (StrLength(cite1) > 0)
  1458.         offSet = Munger((Handle)text, 1, param1, sizeof(param1), (Ptr)&(cite1[1]), StrLength(cite1));
  1459.     newLength = GetHandleSize((Handle)text) - 1;
  1460.     if (newLength > 255)
  1461.         newLength = 255;
  1462.     (*text)[0] = newLength;                        // string’s new length byte
  1463. }
  1464.  
  1465. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1466. /*
  1467. Given a window’s portRect, this routine will center the rectangle on the
  1468. main device within the desktop region.  This excludes the menu bar area.
  1469. It follows the Apple Human Interface Guidelines for where to place a
  1470. window centered on the screen. That is, 1/3th of the desktop shows above
  1471. the window and 2/3ths below it.  It returns a new rectangle set to the
  1472. centered position.  The top and left of this rectangle can be used with
  1473. MoveWindow.  This routine only considers the main device.  CenterWindowRect
  1474. could take a GDevice as a parameter to center the VAR rect with.  This
  1475. would also allow Alerts or dialogs that are closely associated with a
  1476. document window to be centered relative to the monitor that contains that
  1477. document.  This is also one of the Human Interface Guidelines.  MacApp 2.0
  1478. has a utility CalcScreenRect that you can borrow from to include this.
  1479.  
  1480. WARNING: This routine may move or purge memory.
  1481. */
  1482.  
  1483. #pragma segment Open
  1484. void CenterWindowRect(short variant, Rect *theRect)
  1485. {
  1486.     Point rectSize;
  1487.     short wTitleHeight;
  1488.  
  1489.     wTitleHeight = GetWTitleHeight(variant);        //get title height
  1490.     SetPt(&rectSize, theRect->right, theRect->bottom + wTitleHeight);    //get size of rect
  1491.     SubPt(TopLeft(*theRect), &rectSize);            // include it in size
  1492.  
  1493.     theRect->top = ((qd.screenBits.bounds.bottom - qd.screenBits.bounds.top - GetMBarHeight() - rectSize.v)
  1494.                         / 3) + GetMBarHeight();        //1/3th below menubar, centered horz
  1495.     theRect->left = ((qd.screenBits.bounds.right - qd.screenBits.bounds.left) - rectSize.h) / 2;
  1496.  
  1497.     SetPt(&BottomRight(*theRect), theRect->left, theRect->top);    //return adjusted rect
  1498.     AddPt(rectSize, &BottomRight(*theRect));
  1499.     theRect->top += wTitleHeight;                    //remove title height from rect
  1500. }
  1501.  
  1502. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1503. /*
  1504. Given a window ID, this routine will center the window’s rectangle before
  1505. showing it on the main screen.  This follows the Apple Human Interface
  1506. Guidelines for where to place a centered window on the screen.  If the
  1507. window is closely associated with another window, considerations should be
  1508. given to where that other window is located.  If this other window is on a
  1509. screen other then the main monitor, then it would be best to center the
  1510. window on that other monitor.
  1511.  
  1512. VERSION 1.1: There is a problem with GetNewWindow in the old Mac Plus and
  1513. SE ROMS.  It will call ReleaseResource on the 'WIND' resource within
  1514. GetNewWindow.  This make the handle invalid after the call.  Therefore,
  1515. before calling HPurge, we get the resource once again.
  1516. */
  1517.  
  1518. #pragma segment Open
  1519. WindowPtr GetCenteredWindow(short id, Ptr p, WindowPtr behind)
  1520. {
  1521.     Rect newRect;
  1522.     WindowTHndl windTemplate;
  1523.     WindowPtr window;
  1524.  
  1525.     window = nil;                                                //initialize result
  1526.     windTemplate = (WindowTHndl)(Get1Resource('WIND', id));
  1527.     if (windTemplate != nil) {
  1528.         HNoPurge((Handle)windTemplate);
  1529.         newRect = (**windTemplate).boundsRect;
  1530.         CenterWindowRect((**windTemplate).procID, &newRect);
  1531.         (**windTemplate).boundsRect = newRect;
  1532.         window = GetNewWindow(id, p, behind);
  1533.         HPurge(Get1Resource('WIND', id));
  1534.     }
  1535.     return(window);
  1536. }
  1537.  
  1538. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1539. /*
  1540. Given an Alert ID, this routine will center the alert’s rectangle before
  1541. showing it on the main screen.  This follows the Apple Human Interface
  1542. Guidelines for where to place a centered window on the screen.  If the
  1543. Alert is closely associated with another window, considerations should be
  1544. given to what the window is located.  If this other window is on a screen
  1545. other then the main monitor, then it would be best to center the Alert on
  1546. that other monitor.
  1547.  
  1548. VERSION 1.2: System 7 supports auto-positioning of windows and dialog, so
  1549. this routine isn't being called anymore. I has been left in the sources
  1550. for the readers that may be interested in knowing how it works.
  1551. */
  1552.  
  1553. #pragma segment Open
  1554. short CenteredAlert(short alertID)
  1555. {
  1556.     AlertTHndl alertHandle;
  1557.     Rect alertRect;
  1558.  
  1559.     alertHandle = (AlertTHndl)(Get1Resource('ALRT', alertID));
  1560.     if (alertHandle != nil) {
  1561.         HNoPurge((Handle)alertHandle);
  1562.         alertRect = (**alertHandle).boundsRect;
  1563.         CenterWindowRect(dBoxProc, &alertRect);
  1564.         (**alertHandle).boundsRect = alertRect;
  1565.         HPurge((Handle)alertHandle);
  1566.     }
  1567.     return(Alert(alertID, nil));
  1568. }
  1569.  
  1570. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1571. /*
  1572. Given a dialog ID, this routine will center the dialog’s rectangle before
  1573. showing it on the main screen.  This follows the Apple Human Interface
  1574. Guidelines for where to place a centered window on the screen.  If the
  1575. dialog is closely associated with another window, considerations should be
  1576. given to what the window is located.  If this other window is on a screen
  1577. other then the main monitor, then it would be best to center the dialog on
  1578. that other monitor.
  1579.  
  1580. VERSION 1.1: There is a problem with GetNewDialog in the old Mac Plus and
  1581. SE ROMS.  It will call ReleaseResource on the 'WIND' resource within
  1582. GetNewWindow.  This make the handle invalid after the call.  Therefore,
  1583. before calling HPurge, we get the resource once again.
  1584.  
  1585. VERSION 1.2: System 7 supports auto-positioning of windows and dialog, so
  1586. this routine isn't being called anymore. I has been left in the sources
  1587. for the readers that may be interested in knowing how it works.
  1588. */
  1589.  
  1590. #pragma segment Open
  1591. DialogPtr GetCenteredDialog(short id, Ptr p, WindowPtr behind)
  1592. {
  1593.     Rect newRect;
  1594.     DialogTHndl dlogTemplate;
  1595.     DialogPtr dialog;
  1596.  
  1597.     dialog = nil;                                                //initialize result
  1598.     dlogTemplate = (DialogTHndl)Get1Resource('DLOG', id);
  1599.     if (dlogTemplate != nil) {
  1600.         newRect = (**dlogTemplate).boundsRect;
  1601.         CenterWindowRect((**dlogTemplate).procID, &newRect);
  1602.         (**dlogTemplate).boundsRect = newRect;
  1603.         HNoPurge((Handle)dlogTemplate);
  1604.         dialog = GetNewDialog(id, p, behind);
  1605.         HPurge(Get1Resource('DLOG', id));
  1606.     }
  1607.     return(dialog);
  1608. }
  1609.  
  1610. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1611. /*
  1612. Given any control handle, this will draw an outline around it.  This is
  1613. used for the default button of a window.  The extra nice feature here is
  1614. that I’ll erase the outline for buttons that are inactive.  Seems like
  1615. there should be a Toolbox call for getting a control’s hilite state.
  1616. Since there isn’t, I have to look into the control record myself.  This
  1617. should be called for update and activate events.
  1618.  
  1619. The method for determining the oval diameters for the roundrect is a
  1620. little different than that recommended by Inside Mac.  IM I-407 suggests
  1621. that you use a hardcoded (16,16) for the diameters. However, this only
  1622. looks good for small roundrects. For larger ones, the outline doesn’t
  1623. follow the inner roundrect because the CDEF for simply buttons doesn’t
  1624. use (16,16). Instead, it uses half the height of the button as the
  1625. diameter. By using this formula, too, our outlines look better.
  1626.  
  1627. WARNING: This will set the current port to the control’s window.
  1628. */
  1629.  
  1630. #pragma segment Main
  1631. void DoButtonOutline(ControlHandle button)
  1632. {
  1633.     Rect theRect;
  1634.     PenState curPen;
  1635.     short buttonOval;
  1636.  
  1637.     if (button != nil) {
  1638.         SetPort((**button).contrlOwner);
  1639.         GetPenState(&curPen);
  1640.         PenNormal();
  1641.         theRect = (**button).contrlRect;
  1642.         InsetRect(&theRect, kButtonFrameInset, kButtonFrameInset);
  1643.         buttonOval = (theRect.bottom - theRect.top) / 2;
  1644.         if (((**button).contrlHilite == kNoHiliteControlPart))
  1645.             PenPat(&qd.black);
  1646.         else
  1647.             PenPat(&qd.gray);
  1648.         PenSize(kButtonFrameSize, kButtonFrameSize);
  1649.         FrameRoundRect(&theRect, buttonOval, buttonOval);
  1650.         SetPenState(&curPen);
  1651.     }
  1652. }
  1653.  
  1654. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1655. /*
  1656. Given the button control handle, this will cause the button to look as
  1657. if it has been clicked in.  This is nice to do for the user if they type
  1658. return or enter to select the default item.
  1659. */
  1660.  
  1661. #pragma segment Main
  1662. void SelectButton(ControlHandle button)
  1663. {
  1664.     long finalTicks;
  1665.  
  1666.     HiliteControl(button, kHiliteControlSelect);
  1667.     Delay(kDelayTime, &finalTicks);
  1668.     HiliteControl(button, kHiliteControlDeselect);
  1669. }
  1670.  
  1671. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1672. /*
  1673. Given a sound document, this routine goes through the control list looking
  1674. for a control that needs to be activated, or deactivated what ever the
  1675. case maybe.  The Play button is the default button, and it also needs its
  1676. outline drawn.  The Stop button is always active on the front most window.
  1677. This is true even if there is no selection made in the window’s list.  The
  1678. Stop button is a global action, regardless of what is happening in the window.
  1679.  
  1680. VERSION 1.1:  Stop button is only active while a sound is playing.
  1681. */
  1682.  
  1683. #pragma segment Main
  1684. void ActivateSndCntls(SndDocPeek sndDoc)
  1685. {
  1686.     ControlHandle control;
  1687.     long cntlRefCon;
  1688.     Boolean activate;
  1689.  
  1690.     activate = ((WindowPeek)sndDoc)->hilited && HasSelection(sndDoc);
  1691.     control = ((WindowPeek)sndDoc)->controlList;
  1692.     while (control != nil) {
  1693.         cntlRefCon = GetControlReference(control);
  1694.         if (    (cntlRefCon == rPlaySndCntl)
  1695.              || (cntlRefCon == rHyperPlayCntl)
  1696.              || (cntlRefCon == rPlayScaleCntl)
  1697.              || (cntlRefCon == rMelodyCntl)) {
  1698.             if (activate)
  1699.                 HiliteControl(control, kNoHiliteControlPart);
  1700.             else
  1701.                 HiliteControl(control, kControlInactiveControlPart);
  1702.             if (cntlRefCon == rPlaySndCntl)
  1703.                 DoButtonOutline(control);
  1704.         }
  1705.         if (cntlRefCon == rStopCntl) {
  1706.             if (((WindowPeek)sndDoc)->hilited && HasChannelOpen())
  1707.                 HiliteControl(control, kNoHiliteControlPart);
  1708.             else
  1709.                 HiliteControl(control, kControlInactiveControlPart);
  1710.         } //cntlRefCon == rStopCntl
  1711.         if (cntlRefCon == rRecordCntl) {
  1712.             if (((WindowPeek)sndDoc)->hilited)
  1713.                 HiliteControl(control, kNoHiliteControlPart);
  1714.             else
  1715.                 HiliteControl(control, kControlInactiveControlPart);
  1716.         } //cntlRefCon == rRecordCntl
  1717.         control = (**control).nextControl;
  1718.     }
  1719. }
  1720.  
  1721. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1722. /*
  1723. Check if a window belongs to a desk accessory.  This will first test for a
  1724. nil window.  DAs will have a negitive windowKind.
  1725. */
  1726.  
  1727. #pragma segment Main
  1728. Boolean IsDAWindow(WindowPtr window)
  1729. {
  1730.     if (window == nil)
  1731.         return(false);
  1732.     else    //DA windows have negative windowKinds
  1733.         return(((WindowPeek)window)->windowKind < 0);
  1734. }
  1735.  
  1736. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1737. /*
  1738. Check to see if a window is a document window.  This will first test for a
  1739. nil window.  Document windows all have a refCon set to rSoundWindow.  This
  1740. insures there is document type information after the window record.
  1741. */
  1742.  
  1743. #pragma segment Main
  1744. Boolean IsDocWindow(WindowPtr window)
  1745. {
  1746.     Boolean    result;
  1747.  
  1748.     if (window == nil)
  1749.         result = false;
  1750.     else
  1751.         result = (GetWRefCon(window) == rSoundWindow);
  1752.     return(result);
  1753. }
  1754.  
  1755. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1756. /*
  1757. This test will return true for a window that needs to be treated as a
  1758. modal window.  To include additional windows, add the window’s refCon
  1759. value to the set of modal windows.
  1760. */
  1761.  
  1762. #pragma segment Main
  1763. Boolean IsModalWindow(WindowPtr window)
  1764. {
  1765.     long    wRef;
  1766.     Boolean    result;
  1767.  
  1768.     if (window == nil)
  1769.         result = false;
  1770.     else {
  1771.         wRef = GetWRefCon(window);
  1772.         result = (wRef == rAboutWindow);         // test for all our modal windows
  1773.     }
  1774.     return(result);
  1775. }
  1776.  
  1777. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1778. /*
  1779. This is used to free up all the sound data and channels in use.  There are
  1780. two reasons for using this routine.  One is after a sound has completed
  1781. the other is to terminate a sound that may be in progress.  For this
  1782. second reason, this routine should always be used before playing a new
  1783. sound.  When disposing of a sound channel, this sets all document’s
  1784. sndInUse flags to false.  I can call this routine at any time.  When I
  1785. find that the memory reserve is used, I will call this to free up any
  1786. possible sound resources which tend to be large.  Another important point
  1787. is the human interface issue of hiding the status window.  It’s possible
  1788. that  playing some short sounds could cause the status window to disappear
  1789. before the user had much of a chance to see it.  To solve this, there is a
  1790. delay that insures the status window was visible for a minimal time.
  1791.  
  1792. VERSION 1.1:  Update the document's controls since know that the sound
  1793. is no longer playing the buttons in the document window need to be updated.
  1794. */
  1795.  
  1796. #pragma segment Main
  1797. void KillSound(void)
  1798. {
  1799.     WindowPtr window;
  1800.  
  1801.     if (HasChannelOpen()) {                            // if a channel is open...
  1802.         window = FrontWindow();                        // set all document’s flags
  1803.         while (window != nil) {
  1804.             if (IsDocWindow(window))                // if this is my document...
  1805.                 ((SndDocPeek)window)->sndInUse = false; // then it is no longer active
  1806.             window = (WindowPtr)((WindowPeek)window)->nextWindow;
  1807.         }
  1808.     }
  1809.     if (HasSoundCompleted()) {                        // why were we called?
  1810.         DoSoundComplete();                            // from completion
  1811.  
  1812.         // delay for status window to show a minimal amount of time
  1813.         while ((TickCount() - gStatusWindow->showTime) < kShowTimeDelay) {};
  1814.     }
  1815.     else
  1816.         FreeAllChans();                                // or just because
  1817.     HideWindow((WindowPtr)gStatusWindow);
  1818.     window = FrontWindow();
  1819.     if (IsDocWindow(window))
  1820.         ActivateSndCntls((SndDocPeek)window);
  1821. }
  1822.  
  1823. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1824. /*
  1825. Close any of my windows.  At this point, if there was a document
  1826. associated with a window, you could do any document saving processing if
  1827. it has been changed since being opened.  DoCloseWindow would return true
  1828. if the window actually closed.  false would be returned, as an example, if
  1829. the user chose “Cancel” in the Save As… dialog.  An important detail is if
  1830. the window to be close is one of my documents that is currently playing a
  1831. sound.  We cannot dispose of it yet, since the sound resource owned by the
  1832. document would be in use by the Sound Manager.  So, I test the document’s
  1833. sndInUse flag and if it is in use I call KillSound.  The About window
  1834. contains some resource handles.  I only mark them purgeable, but I could
  1835. use ReleaseResource and then DisposHandle.
  1836. */
  1837.  
  1838. #pragma segment Main
  1839. Boolean DoCloseWindow(WindowPtr window)
  1840. {
  1841.     Boolean        result = true;
  1842.  
  1843.     switch (GetWRefCon(window)) {
  1844.  
  1845.         case rSoundWindow:
  1846.             if (((SndDocPeek)window)->sndInUse)        // contain a snd in use?
  1847.                 KillSound();                        // not any more
  1848.             if (((SndDocPeek)window)->list != nil)    // dispose of document data
  1849.                 LDispose(((SndDocPeek)window)->list);
  1850.             if (((SndDocPeek)window)->resFile != 0)
  1851.                 CloseResFile(((SndDocPeek)window)->resFile);
  1852.             CloseWindow((WindowPtr)window);
  1853.             DisposePtr((Ptr)window);                // dispose of our doc storage
  1854.             break;
  1855.  
  1856.         case rStatusWindow:
  1857.             DisposeHandle((Handle)gStatusWindow->message);
  1858.             CloseWindow((WindowPtr)gStatusWindow);
  1859.             DisposePtr((Ptr)gStatusWindow);
  1860.             break;
  1861.  
  1862.         case rAboutWindow:
  1863.             if (((AboutWPeek)window)->comment != nil)    // never dispose...
  1864.                 HPurge(((AboutWPeek)window)->comment);    // a Resource handle
  1865.             if (((AboutWPeek)window)->appPict != nil)
  1866.                 HPurge(((AboutWPeek)window)->appPict);
  1867.             CloseWindow((WindowPtr)window);
  1868.             DisposePtr((Ptr)window);
  1869.             break;
  1870.  
  1871.         default:
  1872.             if (IsDAWindow(window))                        // we can close DAs too
  1873.                 CloseDeskAcc(((WindowPeek)window)->windowKind);
  1874.             break;
  1875.  
  1876.     } // switch GetWRefCon(window)
  1877.     return(result);
  1878. }
  1879.  
  1880. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1881. /*
  1882. Display an alert to inform the user of an error.  MessageID acts as an
  1883. index into a STR# resource of error messages.  This will attempt to
  1884. replace the error number with a string if the sound is a Sound Manager
  1885. error.
  1886.  
  1887. BUG NOTE: GetIndString will return a bogus string if the index is not
  1888. positive.
  1889. */
  1890.  
  1891. #pragma segment Main
  1892. void AlertUser(short error, short messageID)
  1893. {
  1894.     Str255    msg1;
  1895.     Str255    msg2;
  1896.     short    theItem;
  1897.  
  1898.     UseResFile(gAppResRef);                        // restore our resource file
  1899.     if (messageID > 0)
  1900.         GetIndString(msg1, sErrStrings, messageID);
  1901.     else
  1902.         msg1[0] = 0;                            // case there’s no message
  1903.     if ((error <= noHardware) && (error >= badFormat))
  1904.         GetIndString(msg2, sSMErrStrings, AbsoluteValue(error) + noHardware + 1);
  1905.     else
  1906.         NumToString(error, msg2);
  1907.     ParamText(msg1, msg2, "\p", "\p");
  1908.     theItem = Alert(rUserAlert, nil);
  1909. }
  1910.  
  1911. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1912. /*
  1913. Display an alert that tells the user an error occurred, then exit the
  1914. program.  This routine is used as an ultimate bail-out for serious errors
  1915. that prohibit the continuation of the application.  Errors that do not
  1916. require the termination of the application are handled with AlertUser.
  1917. Error checking and reporting has a place even in the simplest application.
  1918.  
  1919. BUG NOTE: GetIndString will return a bogus string if the index is not
  1920. positive.
  1921. */
  1922.  
  1923. #pragma segment Main
  1924. void EmergencyExit(short message)
  1925. {
  1926.     Str255    msg;
  1927.     short    theItem;
  1928.  
  1929.     SetCursor(&qd.arrow);
  1930.     if (message > 0)
  1931.         GetIndString(msg, sErrStrings, message);
  1932.     else
  1933.         msg[0] = 0;                                // case there’s no message
  1934.     ParamText(msg, "\p", "\p", "\p");
  1935.     theItem = Alert(rExitAlert, nil);
  1936.     ExitToShell();                                //gotta go!
  1937. }
  1938.  
  1939. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1940. /*
  1941. Clean up the application and exit.  I close all of the windows so that
  1942. they can update their documents, if any.  Dispose of the SoundUnit and
  1943. close the status window if the user really wants to quit.
  1944. */
  1945.  
  1946. #pragma segment Main
  1947. void Terminate(void)
  1948. {
  1949.     WindowPtr aWindow;
  1950.     Boolean closed;
  1951.  
  1952.     closed = true;
  1953.     KillSound();                                    //stop any sound in progress
  1954.     do {
  1955.         aWindow = FrontWindow();                    //get the current front window
  1956.         if (aWindow != nil)
  1957.             closed = DoCloseWindow(aWindow);        //close this window
  1958.     } while ((closed) && (aWindow != nil));            //do all windows
  1959.     if (closed) {
  1960.         FreeSoundUnit();                            //get rid of all sound equipment
  1961.         gTerminate = true;                            //exit if no cancellation
  1962.     }
  1963. }
  1964.  
  1965. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1966. /*
  1967. Draw the status message and update the button.  I setup a rectangle that
  1968. is the text area.  This is the entire window area, inset a small amount.
  1969. I also subtract from the bottom of the rectangle the area used by the button.
  1970. Doing things this way allowed me to change the size of the window and
  1971. not change any of this code.  The rectangle used by text will fill the
  1972. window regardless of the new size I give it in the WIND resource.
  1973. */
  1974.  
  1975. #pragma segment Main
  1976. void DrawStatusWindow(void)
  1977. {
  1978.     Rect    theRect;
  1979.  
  1980.     PenNormal();
  1981.     theRect = ((WindowPtr)gStatusWindow)->portRect;
  1982.     theRect.bottom = theRect.bottom - kDafaultButSizeH;
  1983.     InsetRect(&theRect, kStadardWhiteSpacing, kStadardWhiteSpacing);
  1984.     HLock((Handle)gStatusWindow->message);
  1985.     TETextBox((*(gStatusWindow->message) + 1),
  1986.                 **(gStatusWindow->message), &theRect, teJustLeft);
  1987.     HUnlock(((Handle)gStatusWindow->message));
  1988.     UpdateControls((WindowPtr)gStatusWindow, ((WindowPtr)gStatusWindow)->visRgn);
  1989.     DoButtonOutline(((WindowPeek)gStatusWindow)->controlList);
  1990. }
  1991.  
  1992. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1993. /*
  1994. Whenever I start a sound, I show this status window.  It will contain a
  1995. message showing what is currently happening.  A very good rule is not to
  1996. perform drawing outside of the update event.  I bend that rule here.  I
  1997. could simply select this window and show it.  This would allow my normal
  1998. drawing and activating routines to be called.  The problem with that was,
  1999. I found that many times the status window appeared and then disappeared
  2000. before these update routines had a chance to draw the window.  This left
  2001. the user with a blank window that immediately went away.  So, I do the
  2002. activation and drawing right after showing it.
  2003.  
  2004. BUG NOTE: GetIndString will return a bogus string if the index is not
  2005. positive.
  2006. */
  2007.  
  2008. #pragma segment Main
  2009. void ShowStatusWindow(short messageID)
  2010. {
  2011.     Str255    msg;
  2012.  
  2013.     if (FailLowMemory(0)) {                            // running low on memory?
  2014.         KillSound();
  2015.         AlertUser(memFullErr, sLowMemory);
  2016.     }
  2017.     else {
  2018.         if (messageID > 0)
  2019.             GetIndString(msg, sMsgStrings, messageID);
  2020.         else
  2021.             msg[0] = 0;                                    // case there’s no message
  2022.         SetString(gStatusWindow->message, msg);
  2023.         ShowWindow((WindowPtr)gStatusWindow);            // show the window
  2024.         SelectWindow((WindowPtr)gStatusWindow);            // bring it to the front
  2025.         SetPort((WindowPtr)gStatusWindow);
  2026.         EraseRect(&(((WindowPtr)gStatusWindow)->portRect)); // erase old message
  2027.         HiliteControl(((WindowPeek)gStatusWindow)->controlList, kNoHiliteControlPart);
  2028.         DrawStatusWindow();                                // draw the new message
  2029.         ValidRect(&(((WindowPtr)gStatusWindow)->portRect)); // avoid needless update
  2030.         gStatusWindow->showTime = TickCount();            // it’s show time folks
  2031.     }
  2032. }
  2033.  
  2034. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2035. /*
  2036. This initializes the status window, and keeps it hidden until the user
  2037. decides to play a sound.  I also center it for the first time, but the
  2038. user is free to drag it to a new location afterwards.  The status window
  2039. also has a message associated to it, so I allocate a string handle for this.
  2040. */
  2041.  
  2042. #pragma segment Initialize
  2043. void InitStatusWindow(void)
  2044. {
  2045.     WindowPtr        window;
  2046.     ControlHandle    stopButton;
  2047.  
  2048.     gStatusWindow = (StatWindowPeek)NewPtrClear(sizeof(StatusWindow));
  2049.     if (gStatusWindow == nil)
  2050.         EmergencyExit(sInitStatusErr);
  2051.     window = GetNewWindow(rStatusWindow, (Ptr)gStatusWindow, (WindowPtr)-1);
  2052.     if (window == nil)
  2053.         EmergencyExit(sInitStatusErr);
  2054.     SetWRefCon(window, rStatusWindow);
  2055.     stopButton = GetNewControl(rCancelCntl, window);
  2056.     if (stopButton == nil)
  2057.         EmergencyExit(sInitStatusErr);
  2058.     gStatusWindow->message = NewString("\p");    // a new empty string handle
  2059.     if (gStatusWindow->message == nil)
  2060.         EmergencyExit(sInitStatusErr);
  2061.     HNoPurge((Handle)gStatusWindow->message);
  2062. }
  2063.  
  2064. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2065. /*
  2066. I just flash the menubar for a moment instead of playing any sounds.  It’s
  2067. the same effect as setting the sound volume to zero but that would prevent
  2068. me playing my sounds.  Also, it helps to prevent the problem of _SysBeep
  2069. being called by the Dialog Manager while I’m playing a sound.  If my
  2070. application is playing a sound and, for example, the user clicks outside
  2071. of the Standard File dialog ModalDialog calls _SysBeep.  This can cause
  2072. the Mac to crash or trash my channel.
  2073.  
  2074. BUG NOTE: If the current Sound Manager were playing a sound and a
  2075. _SysBeep were to occur, bad things could happen on a Mac Plus/SE.  Either
  2076. the application’s channel would be trashed or the Mac could crash.
  2077.  
  2078. VERSION 1.1: The new Sound Manager will handle the problem presented in the
  2079. older Sound Manager regarding SysBeep.  If I'm running under the new Sound
  2080. Manager then I'll call SysBeep anyway.  The new Sound Manager will properly
  2081. handle the situation of an open sound channel when SysBeep is called.
  2082.  
  2083. VERSION 1.2: This is no longer needed since we only use Sound Manager 2 or later.
  2084. */
  2085.  
  2086. #pragma segment Main
  2087. pascal void DoErrorSound(short soundNo)
  2088. {
  2089.     long finalTicks;
  2090.  
  2091.     if (GetSoundMgrVersion() == 1) {
  2092.         if (soundNo > 0) {                        //only after the first time
  2093.             FlashMenuBar(0);
  2094.             Delay(kDelayTime, &finalTicks);
  2095.             FlashMenuBar(0);
  2096.         }
  2097.     }
  2098.     else
  2099.         SysBeep(30);                            //does the right thing now
  2100. }
  2101.  
  2102. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2103. /*
  2104. This tests if the volume has been set very low.  It’s a judgement call as
  2105. to what “too low” means.  The user sets the volume, so how could it be too
  2106. low? A setting of 0 could be considered too low, but in any case do not
  2107. adjust it.  I find the volume goes below 4 on a standard Mac, then there
  2108. is a perceivable difference.  I also wanted to show this routine as a
  2109. demonstration in how to handle such a situation.  Do not change it
  2110. directly! Ask the user to do it.  Consider the user that has their Mac
  2111. connected to a stack of Marshalls (which go to 11) and has purposely set
  2112. their Mac’s volume where they wanted it.  This may be 3 and if you crack
  2113. it up to 8, it could be a scene out of Back to the Future.
  2114.  
  2115. User Interface rule, let the user remain in control!
  2116.  
  2117. VERSION 1.2: Don't use GetSoundVol anymore. This just reads a low memory global
  2118. which is no longer valid with Sound Manager 3 or later. The old low global
  2119. is only 3 bits, which is why the range used to be 0-7. There are new machines
  2120. supported by Sound Manager 3 that have more than 8 volume level. So the
  2121. old global is scaled into the 0-7 range. You should avoid all of this non-sense
  2122. and start using the new calls of Sound Manager 3, and start using percentages.
  2123. */
  2124.  
  2125. #pragma segment Initialize
  2126. void CheckSoundVolume(void)
  2127. {
  2128.     Fixed    percent;
  2129.     long    soundVolume;
  2130.     short    curVolume;
  2131.     short    item;
  2132.     Str255    numStr;
  2133.  
  2134.     if (GetSoundMgrVersion() < 3)
  2135.     {
  2136.         // turn old 0-7 range into the new 0-0x0100 range
  2137.  
  2138.         curVolume = LMGetSdVolume();                // same as calling GetSoundVol()
  2139.         curVolume *= kFullVolume;
  2140.         curVolume += 7-1;                            // avoid rounding errors
  2141.         curVolume /= 7;                                // divid by old max
  2142.     }
  2143.     else
  2144.     {
  2145.         // get the current stereo levels and average them
  2146.  
  2147.         GetDefaultOutputVolume(&soundVolume);
  2148.         curVolume = (soundVolume >> 16) + (soundVolume & 0x0000FFFF);
  2149.         curVolume /= 2;
  2150.     }
  2151.  
  2152.     // take the current level and turn this in to a percentage of full volume,
  2153.     // then show the user that the level is low and how they may increase it.
  2154.  
  2155.     percent = FixDiv((long)curVolume << 16, kFullVolume << 16);
  2156.     percent = FixMul(percent, (long)100 << 16);
  2157.     NumToString(percent >> 16, numStr);
  2158.     ParamText(numStr, "\p", "\p", "\p");        // show the current volume
  2159.     item = Alert(rSoundVolAlert, nil);
  2160. }
  2161.  
  2162. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2163. /*
  2164. Simply add all the buttons I want to use in the document window.  I get
  2165. the control templates, and add the control to the window.  I don’t check
  2166. for getting errors for two reasons.  I put these controls there.  If the
  2167. user removes them, it’s their problem.  If the application cannot get the
  2168. memory required for these, then the application is already out of memory
  2169. and checking here probably wouldn’t help much.  I do check before creating
  2170. the document that there was enough available memory.  I save the resource
  2171. ID into the control’s refCon so later I can tell which control was hit by
  2172. the user.
  2173.  
  2174. VERSION 1.1: Add the record button to the window.}
  2175. */
  2176.  
  2177. #pragma segment Open
  2178. void AddSndDocControls(WindowPtr window)
  2179. {
  2180.     ControlHandle    control;
  2181.  
  2182.     control = GetNewControl(rPlaySndCntl, window);
  2183.     if (control != nil)
  2184.         SetControlReference(control, rPlaySndCntl);
  2185.  
  2186.     control = GetNewControl(rHyperPlayCntl, window);
  2187.     if (control != nil)
  2188.         SetControlReference(control, rHyperPlayCntl);
  2189.  
  2190.     control = GetNewControl(rPlayScaleCntl, window);
  2191.     if (control != nil)
  2192.         SetControlReference(control, rPlayScaleCntl);
  2193.  
  2194.     control = GetNewControl(rMelodyCntl, window);
  2195.     if (control != nil)
  2196.         SetControlReference(control, rMelodyCntl);
  2197.  
  2198.     control = GetNewControl(rStopCntl, window);
  2199.     if (control != nil)
  2200.         SetControlReference(control, rStopCntl);
  2201.  
  2202.     control = GetNewControl(rRecordCntl, window);
  2203.     if (control != nil)
  2204.         SetControlReference(control, rRecordCntl);
  2205. }
  2206.  
  2207. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2208. #pragma segment Open
  2209. Boolean PositionAvailable(Point newPt, short wTitleHeight)
  2210. {
  2211.     WindowPtr oldWindow;
  2212.     Point delta;
  2213.     Point oldPt;
  2214.     Boolean taken;
  2215.  
  2216.     taken = false;
  2217.     oldWindow = FrontWindow();
  2218.     while ((oldWindow != nil) && !taken) {
  2219.         if (IsDocWindow(oldWindow) && ((WindowPeek)oldWindow)->visible) {
  2220.             oldPt = GetGlobalTopLeft(oldWindow);
  2221.             delta.v = AbsoluteValue(newPt.v - oldPt.v);
  2222.             delta.h = AbsoluteValue(newPt.h - oldPt.h);
  2223.             taken = (delta.h + delta.v)
  2224.                         <= ((kWindowPosStaggerH + kWindowPosStaggerV + wTitleHeight) / 2);
  2225.         }
  2226.         oldWindow = (WindowPtr)(((WindowPeek)oldWindow)->nextWindow);
  2227.     }
  2228.     return(!taken);
  2229. }
  2230.  
  2231. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2232. /*
  2233. There are a few approaches I thought of and tried, none of which satisfied
  2234. me.  ResEdit uses the simplest method, an offset from the front window.  I
  2235. don’t like this because it may cover up existing windows.  So my approach is
  2236. to go through the window list looking for an empty spot.  If the empty
  2237. spot causes the window to go off the screen, then I start a new stack off
  2238. to the right.  This will cause a new series of window to be stacked.  If the
  2239. new stack would cause the window to go off screen, then it’s time to give up.
  2240. All new windows will then be moved to the starting point and there is no
  2241. further stacking.  If I look at this routine too much, I get real tired.
  2242. If I try to change it at all, I get woozy trying to make it work again.
  2243.  
  2244. VERSION 1.2: System 7 provides for staggering windows, but it doesn't work
  2245. exactly the way I want it. Mine's better.
  2246. */
  2247.  
  2248. #pragma segment Open
  2249. WindowPtr NewStackedWindow(short windID, Ptr windStorage)
  2250. {
  2251.     Point windSize;
  2252.     Point newPt;
  2253.     WindowPtr newWindow;
  2254.     short wTitleHeight;
  2255.     short numStacks;
  2256.     Boolean taken;
  2257.  
  2258.     //set the initail starting point for a window, less the staggering amount
  2259.     newWindow = GetNewWindow(windID, windStorage, (WindowPtr)-1);
  2260.     windSize = BottomRight(newWindow->portRect);
  2261.     SubPt(TopLeft(newWindow->portRect), &windSize);
  2262.     wTitleHeight = GetWTitleHeight(GetWVariant(newWindow));
  2263.     SetPt(&newPt, kWindowPosStartPt - kWindowPosStaggerH, kWindowPosStartPt - kWindowPosStaggerV + GetMBarHeight());
  2264.  
  2265.     numStacks = 0;
  2266.     do { //add the staggering amount then test if the window goes off the screen
  2267.         taken = true;
  2268.         SetPt(&newPt, newPt.h + kWindowPosStaggerH, newPt.v + kWindowPosStaggerV + wTitleHeight);
  2269.         if (    ((newPt.v + windSize.v) > qd.screenBits.bounds.bottom)
  2270.              || ((newPt.h + windSize.h) > qd.screenBits.bounds.right))
  2271.         {
  2272.             numStacks++;
  2273.             SetPt(&newPt, kWindowPosStartPt + (numStacks * kWindowPosStaggerH),
  2274.                             kWindowPosStartPt + wTitleHeight + GetMBarHeight());
  2275.             if (((newPt.v + windSize.v) > qd.screenBits.bounds.bottom)
  2276.                         || ((newPt.h + windSize.h) > qd.screenBits.bounds.right)) {
  2277.                 SetPt(&newPt, kWindowPosStartPt, kWindowPosStartPt + wTitleHeight + GetMBarHeight());
  2278.                 taken = false;
  2279.             }
  2280.         }
  2281.     } while (taken && !(PositionAvailable(newPt, wTitleHeight)));
  2282.  
  2283.     MoveWindow(newWindow, newPt.h, newPt.v, true);
  2284.     return(newWindow);
  2285. }
  2286.  
  2287. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2288. /*
  2289. Create a new sound document and initialize all the data associated with
  2290. it.  If I encounter an error, then dispose of any memory allocated in the
  2291. attempt.  Allocate all necessary memory required for a sound document.  I
  2292. like to use NewPtrClear because is will initialize all the memory.  I
  2293. depend on real values or zero in my document fields.  Then its time to
  2294. build a sound document which contains all the controls and the list of
  2295. sounds.  I get the position for the list kept in a rectangle resource.
  2296. This  helps me to adjust the size of the list without changing code.  I
  2297. will make  an additional adjustment to the list’s height to insure better
  2298. scrolling.  This avoids the ugly white space of an improperly sized
  2299. rectangle.  If I cannot create the list, I’ll close the window and return
  2300. a NIL.  If I find after creating a new document that I’m low on memory,
  2301. I’ll close it and return an error.
  2302.  
  2303. SPECIAL NOTE: The Human Interface Group suggested that the buttons and the
  2304. list should in the System font.  I designed these windows using the
  2305. application font in 10 point.  If you change the call to TextFont, you
  2306. find that the buttons and the list will work properly in your font choice.
  2307.  
  2308. VERSION 1.1:  Size the window smaller to hide the record button from the
  2309. user if Sound Input is not available.  Added memory check to the beginning
  2310. of this routine before creating the document.  This was done because there
  2311. are two routines that call this one, OpenSoundDoc and NewSoundDoc which
  2312. both performed this check.  Having it here in one place reduces the chance of
  2313. future bugs (doing the same thing in two places) and reduces the size of
  2314. the code.
  2315. */
  2316.  
  2317. #pragma segment Open
  2318. OSErr CreateSoundDoc(short resRef, FSSpecPtr file)
  2319. {
  2320.     SndDocPeek newDocPtr;
  2321.     WindowPtr window;
  2322.     ListHandle list;
  2323.     RectHandle rHandle;
  2324.     Rect lView;
  2325.     Rect lBounds;
  2326.     Cell aCell;
  2327.     Point cSize;
  2328.     Point newSize;
  2329.     short maxListHeight;
  2330.     OSErr theErr;
  2331.     Boolean ignore;
  2332.  
  2333.     if (FailLowMemory(kMemForSndDoc)) {
  2334.         CloseResFile(resRef);                            //close it before leaving
  2335.         return(memFullErr);
  2336.     }
  2337.  
  2338.     theErr = noErr;
  2339.     newDocPtr = (SndDocPeek)NewPtrClear(sizeof(SndDocument));
  2340.     if (newDocPtr != nil) {
  2341.         window = NewStackedWindow(rSoundWindow, (Ptr)newDocPtr);
  2342.         SetPort(window);
  2343.         if (! HasSoundInput())
  2344.             SizeWindow(window, kSoundWindowSizeW,
  2345.                     kSoundWindowSizeH - (kStadardWhiteSpacing + kSndButtonSizeH), false);
  2346.         TextFont(0);                                    //set window to System font, blech
  2347.         AddSndDocControls(window);                        //add my buttons
  2348.         SetWRefCon((WindowPtr)newDocPtr, rSoundWindow); //mark as an app window
  2349.         SetWTitle((WindowPtr)newDocPtr, file->name);
  2350.         newDocPtr->resFile = resRef;                    //save its resource file ref
  2351.         newDocPtr->vRefNum = file->vRefNum;                //save its volume reference
  2352.         newDocPtr->dirID = file->parID;                    //save its directory ID
  2353.         newDocPtr->sndInUse = false;                    //not yet it doesn’t
  2354.         rHandle = (RectHandle)Get1Resource('RECT', rListRectID);
  2355.         if (rHandle != nil) {                            //get the stored list size
  2356.             lView = **rHandle;
  2357.             SetRect(&lBounds, 0, 0, 1, 0);                //one dimentional list
  2358.             SetPt(&cSize, 0, 0);                        //List Mgr will find cell size
  2359.             list = LNew(&lView, &lBounds, cSize, 0, window, false, false, false, true);
  2360.             maxListHeight = window->portRect.bottom - window->portRect.top - (2 * kStadardWhiteSpacing);
  2361.             if (maxListHeight < (lView.bottom - lView.top))
  2362.                 maxListHeight = (lView.bottom - lView.top);
  2363.             if (list != nil) {
  2364.                 newSize.h = (**list).cellSize.h;        //get the width of one cell
  2365.                 newSize.v = maxListHeight;                //get the best height for all cells
  2366.                 newSize.v -= maxListHeight % (**list).cellSize.v;
  2367.                 LSize(newSize.h, newSize.v, list);        //adjust for best scrolling
  2368.                 (**list).selFlags = lOnlyOne;            //single selections only
  2369.                 newDocPtr->list = list;                    //save the list handle
  2370.                 theErr = InitSndList(newDocPtr);        //initialize the list data
  2371.                 if (theErr == noErr) {
  2372.                     SetPt(&aCell, 0, 0);                //by default, I will...
  2373.                     LSetSelect(true, aCell, newDocPtr->list); //select first item
  2374.                     LSetDrawingMode(true, newDocPtr->list);
  2375.                     ShowWindow((WindowPtr)newDocPtr);    //get the show on the road
  2376.                     if (FailLowMemory(0))                //if I’m low on memory...
  2377.                         theErr = memFullErr;
  2378.                 }
  2379.             }
  2380.             else
  2381.                 theErr = nilHandleErr;                    //list handle was NIL
  2382.         }
  2383.         else
  2384.             theErr = nilHandleErr;                        //RECT handle was NIL
  2385.         if (theErr != noErr) {                            //could not create the list
  2386.             ignore = DoCloseWindow(window);                //if not, close the window
  2387.             newDocPtr = nil;                            //return nil pointer too
  2388.         }
  2389.     }
  2390.     else                                                //NewPtrClear was nil
  2391.         theErr = MemError();
  2392.     return(theErr);                                        //return the error, if any
  2393. }
  2394.  
  2395. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2396. /*
  2397. Believe it or not, but this turned out to be one of the more difficult
  2398. routines to write.  This was the best approach I could thing of to avoid
  2399. serious problems.  I don’t center the Standard File dialog because it
  2400. wouldn’t be under the File menu where the user probably has the mouse.  I
  2401. use OpenRFPerm because it allows for permissions and doesn’t depend on the
  2402. current directory.  First problem with opening a resource file was to
  2403. check if the file already being open.  Read Tech Notes #116 and #185.
  2404. GetFInfo seems to do the trick.  At least it does report all files that
  2405. are open on this machine.  It may be incorrect for files on AppleShare
  2406. opened by other machines.  What I’m worried about is when opening a
  2407. resource file for the second time on the same machine may return the
  2408. previous opener’s resource map.  This is very dangerous, and if the second
  2409. opener calls CloseResFile there will be a crash.  I want to be friendly
  2410. and if the user tries to open the file the second time and I’ve already
  2411. got it on the screen, then I’ll bring that window forward.  To test for
  2412. this, I compare the file’s name, DirID and vRefNum.  The vRefNum is
  2413. dynamic and needs to be converted into a volume name if I were to save
  2414. this information for future use.
  2415.  
  2416. There are a couple pitfalls, even with this method.  The user can open the
  2417. file, then move it to another directory.  This same thing confuses all
  2418. other applications I’ve tested.  Opening a resource file will allocate a
  2419. resource map handle into my heap, which may be large depending on the
  2420. number of resources in that file.  Additionally this loads all resources
  2421. marked “preload.”  So I set ResLoad to false to prevent the preloading.  I
  2422. test if the amount of memory I believe my document will require is
  2423. available after opening the resource file.  If true, I test if there are
  2424. any sound resources in that file and if not alert the user.  After all
  2425. this, create the new document.  If after creating the new document I find
  2426. that memory is too low, will close it and return an error.  If it does
  2427. fail, then close the file before anything else or there may not be enough
  2428. memory to show the alert.  All of these tests created a number of
  2429. IF-THEN-ELSE blocks and became unyielding.  C programmers get a break, so
  2430. gimme me one too.  I use the MPW Pascal EXIT.
  2431.  
  2432. BUG NOTE: Don’t open a resource file that is already open.  OpenResFile
  2433. may return an existing resource map when it gets opWrErr from the file
  2434. system.  If this happens, the resource file will not be unique and this
  2435. is very bad.  Another problem is if I get a read-only path and someone
  2436. else opens it for read/write.  This is also very bad.  Read Tech Notes
  2437. #116 and #185 hint at this problem, but I think a more comprehensive one
  2438. is in order.
  2439.  
  2440. BUG NOTE: GetWDInfo fails with nsvErr if the working directory returned
  2441. from Standard File is the root of an A/UX volume.  I could work around
  2442. this,  but it would be dependant on the current version of A/UX.  Read
  2443. Tech Note #229.  I believe this is also true for TOPS.
  2444.  
  2445. VERSION 1.1:  I'll open files that do not have snd resources in them.
  2446. This allows users to open existing files and then record or paste a sound
  2447. into it.  I changed OpenRFPerm to HOpenResFile to avoid working
  2448. directories and to be consistent with the rest of the sources.  I
  2449. separated the standard file code from OpenSndDoc to support opening
  2450. documents being open from the Finder.  This allows me to share the same
  2451. routine to open files either from double clicking them in the Finder, or
  2452. by the Standard File dialog.  Removed the checking for low memory conditions
  2453. since CreateSoundDoc is now doing this.
  2454. */
  2455.  
  2456. #pragma segment Open
  2457. void OpenSoundDoc(FSSpecPtr file)
  2458. {
  2459.     HParamBlockRec fPBRec;
  2460.     WindowPtr window;
  2461.     short resFileRef;
  2462.     OSErr theErr;
  2463.  
  2464.     fPBRec.fileParam.ioCompletion = nil;            //prepare a paramBlock
  2465.     fPBRec.fileParam.ioNamePtr = file->name;
  2466.     fPBRec.fileParam.ioVRefNum = file->vRefNum;
  2467.     fPBRec.fileParam.ioDirID = file->parID;
  2468.     fPBRec.fileParam.ioFVersNum = 0;
  2469.     fPBRec.fileParam.ioFDirIndex = 0;
  2470.     theErr = PBHGetFInfoSync(&fPBRec);                 //fPBRec on stack, synch only
  2471.     if (theErr == noErr) {
  2472.         if (fPBRec.fileParam.ioFlAttrib & kResForkOpenBit) {
  2473.             if (OpenByApp(file, &window))
  2474.                 SelectWindow(window);                //I opened this one, select it
  2475.             else
  2476.                 AlertUser(noErr, sCurInUseErr);        //in use by someone else
  2477.             return;
  2478.         }
  2479.     } else {
  2480.         AlertUser(theErr, sStandardErr);            //PBGetFInfo failed
  2481.         return;
  2482.     }
  2483.     SetResLoad(false);                                //don’t load any resources
  2484.     resFileRef = FSpOpenResFile(file, fsCurPerm);
  2485.     theErr = ResError();                            //save error, if any
  2486.     SetResLoad(true);                                //restore ResLoad state
  2487.     UseResFile(gAppResRef);                            //changes ResErr
  2488.     if (resFileRef != kResFileNotOpened)            //error from OpenRFPerm?
  2489.         theErr = CreateSoundDoc(resFileRef, file);
  2490.     if (theErr != noErr)
  2491.         AlertUser(theErr, sNewDocErr);                 //couldn’t create new doc
  2492. }
  2493.  
  2494. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2495. /*
  2496. VERSION 1.1:  Create a new file for the user.  I prefer to use the new
  2497. HCreateResFile to avoid working directories, and to be consistent with
  2498. the rest of the sources.  Also, I need the real vRefNum and dirID to
  2499. store in the sndDoc record.  After creating the file, I set its Finder
  2500. information to the proper type and creator.  Finally, I get to open the
  2501. new file.  After this point everything is exactly like opening an
  2502. existing file and creating a new sound document.
  2503.  
  2504. VERSION 1.2: If dupFNErr is returned by HCreateResFile, then the user
  2505. wants to replace the file with a new SoundApp document.
  2506. */
  2507.  
  2508. #pragma segment Open
  2509. void NewSoundDoc(void)
  2510. {
  2511.     Str255 msg;
  2512.     StandardFileReply reply;
  2513.     StringHandle strHandle;
  2514.     short resFileRef;
  2515.     OSErr theErr;
  2516.  
  2517.     strHandle = (StringHandle)Get1Resource('STR ', rPutFileMsg);
  2518.     if (strHandle != nil)
  2519.         PStringCopy(*strHandle, msg);                        //save no name title
  2520.     else
  2521.         msg[0] = 0;                                            //at least an empty string
  2522.  
  2523.     StandardPutFile(msg, "\p", &reply);
  2524.     if (reply.sfGood)
  2525.     {
  2526.         theErr = noErr;
  2527.         if (reply.sfReplacing)
  2528.             theErr = FSpDelete(&reply.sfFile);                //we're replacing it
  2529.         if (theErr == noErr)
  2530.         {
  2531.             FSpCreateResFile(&reply.sfFile, rAppSignature, rSndAppDocType, reply.sfScript);
  2532.             theErr = ResError();
  2533.             if (theErr == noErr) {
  2534.                 resFileRef = FSpOpenResFile(&reply.sfFile, fsCurPerm);
  2535.                 theErr = ResError();                        //save error, if any
  2536.                 UseResFile(gAppResRef);                        //changes ResErr
  2537.                 if (resFileRef != kResFileNotOpened)        //error from HOpenResFile
  2538.                     theErr = CreateSoundDoc(resFileRef, &reply.sfFile);
  2539.             }
  2540.         }
  2541.         if (theErr != noErr)
  2542.             AlertUser(theErr, sNewDocErr);                         //return the error
  2543.     }
  2544. }
  2545.  
  2546. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2547. /*
  2548. This is much more than what a typical file filter might do but I wanted
  2549. the user to easily find files that contained snd resources.  So, the
  2550. global flag gSndFilesOnly is used to determine when to filter for such
  2551. files.  Otherwise, I show all files and allow the application to report to
  2552. the user the file they’ve just tried to open doesn’t have any sounds to
  2553. play with.  Opening resource forks can be tricky if it’s already open.  It
  2554. would be very bad to use a resource fork that is already open by another
  2555. application.  The problem being that the Resource Manager doesn’t deal
  2556. with multiple users.  Read Tech notes #116 and #185.  There’s also another
  2557. problem.  If a resource fork is opened by two applications and one closes
  2558. the file then the entire resource fork may closed out from underneath the
  2559. other application.  I do, however, want to show the user files that
  2560. contain sound resources even if they are currently open.  I use
  2561. HOpenResFile with read only permission, which will give me a unique
  2562. resource reference.  I look for a 'snd ' resource and then immediately
  2563. close the file.  Do not use a resource file opened with read only
  2564. permission.  Another reason to use HOpenResFile  is to avoid a necessary
  2565. working directory.  I do not have one while in the file filter, but can
  2566. use the DirID.  Performing this search on each file is time consuming so I
  2567. show a spinning cursor to show the user I’m working.   Opening a resource
  2568. fork may load resources mark preload.   To avoid this, I call SetResLoad
  2569. to false.  I bet you thought the Resource Manager was a free lunch.  Ha!
  2570. Read Tech Note #203 for other reasons not to play with resources.
  2571.  
  2572. BUG NOTE: While debugging this routine using heap scramble, I found that
  2573. OpenResFile would not open the requested file.  I’m not sure if this is a
  2574. problem with OpenResFile or SFGetFile.
  2575.  
  2576. VERSION 1.1: The bug mentioned above was found.  The problem is that the
  2577. paramBlock happens to be a re-locatable block in the heap.  Passing the
  2578. de-reference ioNamePtr to HOpenResFile was de-referencing this
  2579. re-locatable paramBlock.  Since HOpenResFile moves memory, the namePtr
  2580. would no longer valid and thus HOpenResFile would fail.  Now I copy the
  2581. name out of the paramBlock and use the local variable in HOpenResFile.
  2582. This problem was fixed in System 7, which no longer passes a re-locatable
  2583. block to the file filter.  The new version allows for any resource file
  2584. to be opened.  Because of this new feature, I only show the user files
  2585. that have a resource fork.
  2586.  
  2587. VERSION 1.2: This is a filter for System 7 now, and I don't show invisible files.
  2588. */
  2589.  
  2590. #pragma segment Open
  2591. pascal Boolean SFFilter(CInfoPBPtr p, Boolean *sndFilesOnly)
  2592. {
  2593. #define kShowIt            false                //false means I do not filter out...
  2594. #define kDoNotShowIt    true                //the file and true means that I do.
  2595.  
  2596.     long oldTicks;
  2597.     short resRef;
  2598.     short curRes;
  2599.     Boolean result;
  2600.  
  2601.     oldTicks = TickCount();
  2602.     result = kDoNotShowIt;                    //don’t show anything until I say so
  2603.  
  2604.     if ( (*sndFilesOnly) && !(p->hFileInfo.ioFlFndrInfo.fdFlags & fInvisible) )
  2605.     {
  2606.         curRes = CurResFile();
  2607.         SetResLoad(false);
  2608.         resRef = HOpenResFile(p->hFileInfo.ioVRefNum, p->hFileInfo.ioFlParID,
  2609.                                 p->hFileInfo.ioNamePtr, fsRdPerm);
  2610.         if (resRef != kResFileNotOpened) {
  2611.             UseResFile(resRef);
  2612.             if (Count1Resources(soundListRsrc) > 0)
  2613.                 result = kShowIt;            //hey, we found a sound in here
  2614.             CloseResFile(resRef);
  2615.         }                                    //restore everything
  2616.         SetResLoad(true);
  2617.         UseResFile(curRes);
  2618.     } else
  2619.     {
  2620.         if (   !(p->hFileInfo.ioFlFndrInfo.fdFlags & fInvisible)    // if not invisible
  2621.              && (p->hFileInfo.ioFlRLgLen > 0) )                        // and has resources
  2622.         {
  2623.             result = kShowIt;
  2624.         }
  2625.     }
  2626.     RotateCursor(TickCount() - oldTicks);
  2627.     return(result);
  2628. }
  2629.  
  2630. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2631. #pragma segment Open
  2632. pascal short SFGetHook(short mySFItem, DialogPtr dialog, void *sndFilesOnly)
  2633. {
  2634.     ControlHandle cntl;
  2635.     Rect r;
  2636.     short kind;
  2637.     short result;
  2638.  
  2639.     result = mySFItem;
  2640.     GetDialogItem(dialog, rSndOnlyCheckBox, &kind, (Handle *)&cntl, &r);
  2641.     switch (mySFItem) {
  2642.  
  2643.         case sfHookFirstCall:
  2644.             if (*(Boolean *)sndFilesOnly)
  2645.                 SetControlValue(cntl, kCntlOn);
  2646.             else
  2647.                 SetControlValue(cntl, kCntlOff);
  2648.             break;
  2649.  
  2650.         case rSndOnlyCheckBox:
  2651.             if (GetControlValue(cntl) == kCntlOff) {
  2652.                 SetControlValue(cntl, kCntlOn);
  2653.                 *(Boolean *)sndFilesOnly = true;
  2654.             }
  2655.             else {
  2656.                 SetControlValue(cntl, kCntlOff);
  2657.                 *(Boolean *)sndFilesOnly = false;
  2658.             }
  2659.             result = sfHookRebuildList;
  2660.             break;
  2661.  
  2662.     }
  2663.     return (result);
  2664. }
  2665.  
  2666. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2667. /*
  2668. VERSION 1.1: This routine used to be inside of OpenSoundDoc.  The
  2669. standard file code was removed to here in order to support opening
  2670. documents being open from the Finder.  I've switched to using a constant
  2671. for the topLeft point of the Standard File dialog.  It saves a few bytes
  2672. of code, so what the hell?  I found a cosmetic problem with the cursor
  2673. not being restored back to arrow after returning Standard File if there
  2674. was an error dialog shown immediately.  So, it is immediately set back to
  2675. the arrow.
  2676. */
  2677.  
  2678. #pragma segment Main
  2679. void GetSoundDoc(void)
  2680. {
  2681.     StandardFileReply reply;
  2682.     SFTypeList typeList;                            //not used, just a placeholder
  2683.     Point sfTopLeft;
  2684.     Boolean sndFilesOnly;
  2685.  
  2686.     SpinCursor(0);                                    //get the spinning cursor ready
  2687.     sfTopLeft.v = -1;                                //-1,-1 means to center the dialog
  2688.     sfTopLeft.h = -1;
  2689.     sndFilesOnly = false;
  2690.     CustomGetFile(GetRoutineAddress(SFFilter), -1, typeList, &reply, rCustomGetFileDLOG,
  2691.             sfTopLeft, GetRoutineAddress(SFGetHook), nil, nil, nil, &sndFilesOnly);
  2692.     SetCursor(&qd.arrow);
  2693.     if (reply.sfGood)
  2694.         OpenSoundDoc(&reply.sfFile);
  2695. }
  2696.  
  2697. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2698. /*
  2699. Draw the contents of the about window in response to an update event.  At
  2700. this point, BeginUpdate has been called which sets the window’s visRgn to
  2701. clip drawing only where it needs to be done.  I have some text to draw, an
  2702. icon, a picture, and a default button with its outline.  If I have a color
  2703. icon handle I’ll call PlotCIcon.  I offset the picture to draw to the
  2704. right of the icon.  The text will appear in a rectangle as large as the
  2705. window, but below the icon and above the button.  This allows me the
  2706. change the text and if needed change the size of the window’s rectangle to
  2707. compensate.  I won’t have to recompile any code.  Some people use dialogs
  2708. because of this, but I’m demonstrating how it can be done without them.  I
  2709. use UpdateControls to avoid needless drawing that happens with DrawControls.
  2710. It not only runs faster but doesn’t flicker.
  2711.  
  2712. VERSION 1.2: Uses System 7's PlotIcon to draw the proper icon.
  2713. */
  2714.  
  2715. #pragma segment Main
  2716. void DrawAboutWindow(WindowPtr window)
  2717. {
  2718.     Rect theRect;
  2719.     OSErr ignoreErr;
  2720.  
  2721.     PenNormal();
  2722.     SetRect(&theRect, kStdAlertIconLeft, kStdAlertIconTop, kStdAlertIconRight, kStdAlertIconBottom);
  2723.     ignoreErr = PlotIconID(&theRect, atNone, ttNone, rMoofIcon);
  2724.  
  2725.     theRect = (**(PicHandle)((AboutWPeek)window)->appPict).picFrame;
  2726.     OffsetRect(&theRect, -theRect.left + kStdAlertIconRight + kStadardWhiteSpacing,
  2727.                             -theRect.top + kStdAlertIconTop);
  2728.     DrawPicture((PicHandle)((AboutWPeek)window)->appPict, &theRect);
  2729.  
  2730.     SetRect(&theRect, window->portRect.left, kStdAlertIconBottom,
  2731.                 window->portRect.right,
  2732.                 window->portRect.bottom - kDafaultButSizeH);
  2733.     InsetRect(&theRect, kStadardWhiteSpacing, kStadardWhiteSpacing);
  2734.     HLock(((AboutWPeek)window)->comment);
  2735.     TETextBox(*(((AboutWPeek)window)->comment) + 1,
  2736.                 StrLength(*(((AboutWPeek)window)->comment)), &theRect, teJustLeft);
  2737.     HUnlock(((AboutWPeek)window)->comment);
  2738.  
  2739.     UpdateControls(window, window->visRgn);
  2740.     DoButtonOutline(((WindowPeek)window)->controlList);
  2741. }
  2742.  
  2743. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2744. /*
  2745. First thing to do is call KillSound to stop any sound in progress and
  2746. close the status window.  If there’s enough memory available, I’ll play
  2747. the about sound.  This is sound is asynchronous and will automatically be
  2748. disposed of when completed.  There is an icon, taken from the bundle
  2749. resources, and a picture.  The text is taken from a string resource.  It
  2750. contains a couple variables just like ParamText would want.  I get the
  2751. current name of the application from the AppParms.  This is the name of
  2752. the application as specified by the user.  I read in my 'vers' resource
  2753. to get the current version to be displayed in the window.  Read Tech Note
  2754. 189 for more details on the vers resource.  I think these are two
  2755. important things to show in the about box.  These two portions of the text
  2756. are put into place with my own version of ParamText.   I tried all of this
  2757. with a standard dialog, but had trouble.  Sometimes the color icon didn’t
  2758. show up.  Trying to center justify text with a statText item wasn’t really
  2759. possible.  On the Mac SE, for some unknown reason, TESetJust failed to
  2760. center the textH of the dialog.  I also found that this call would set the
  2761. dialog’s text back to Chicago even after I had called TextFont.  So, I
  2762. gave up and did everything myself.  This is a demonstration of how to
  2763. create a dialog without using the Dialog Manager.
  2764. */
  2765.  
  2766. #pragma segment Main
  2767. void DoAbout(void)
  2768. {
  2769.     Str255            verNum;
  2770.     Str255            appName;
  2771.     Ptr                aboutPtr;
  2772.     AboutWPeek        aboutPeek;
  2773.     ControlHandle    control;
  2774.     VersRecHndl        curVersion;
  2775.     OSErr            theErr;
  2776.     Boolean            ignore;
  2777.  
  2778.     KillSound();
  2779.     aboutPtr = NewPtrClear(sizeof(AboutWindow));
  2780.     if (aboutPtr != nil) {
  2781.         aboutPeek = (AboutWPeek)GetNewWindow(rAboutWindow, aboutPtr, (WindowPtr)-1);
  2782.         SetWRefCon((WindowPtr)aboutPeek, rAboutWindow);
  2783.         curVersion = (VersRecHndl)Get1Resource('vers', 1);
  2784.         if (curVersion != nil)
  2785.             PStringCopy((**curVersion).shortVersion, verNum); // get version string
  2786.         else
  2787.             verNum[0] = 0;                                // at least initialize it
  2788.         PStringCopy(LMGetCurApName(), appName);
  2789.         aboutPeek->appPict = Get1Resource('PICT', rAppPict);
  2790.         aboutPeek->comment = Get1Resource('STR ', rAboutText);
  2791.         control = GetNewControl(rAboutOkCntl, (WindowPtr)aboutPeek);
  2792.  
  2793.         if (     (aboutPeek->appPict != nil)
  2794.              && (aboutPeek->comment != nil)
  2795.              && (control != nil))
  2796.         {
  2797.             HNoPurge(aboutPeek->appPict);                // must keep them around
  2798.             HNoPurge(aboutPeek->comment);
  2799.             MyParamText((StringHandle)aboutPeek->comment, appName, verNum);
  2800.             if (!FailLowMemory(0))
  2801.                 theErr = AsynchSndPlay((SndListHandle)Get1Resource(soundListRsrc, rMoofSound));
  2802.         }
  2803.         else                                            // couldn’t build window
  2804.             ignore = DoCloseWindow((WindowPtr)aboutPeek);
  2805.     }
  2806. }
  2807.  
  2808. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2809. /*
  2810. Get the selected sound and pass it to AsynchSndPlay or HyperSndPlay.  This
  2811. depends on the normal flag.  HyperCard’s method is not normal.  This is
  2812. the HyperCard version of playing a sound.  It resamples any sound to
  2813. middle C.  If the resource is too large to fit in memory, I’ll get a nil
  2814. handle error from the SoundUnit.  It might be nice to test for this
  2815. before calling the SoundUnit and telling the user they’re too low on
  2816. memory, but the SoundUnit is robust enough and reports the error.  It’s
  2817. important to call KillSound to dispose of any data that was
  2818. allocated if an error were to occur.
  2819. */
  2820.  
  2821. #pragma segment Main
  2822. void PlaySelectedSnd(SndDocPeek sndDoc, short message)
  2823. {
  2824.     SndListHandle    sndHandle;
  2825.     OSErr            theErr;
  2826.  
  2827.     theErr = GetSelection(sndDoc, &sndHandle);
  2828.     if (theErr == noErr) {
  2829.         if (message == sPlayingMsg)
  2830.             theErr = AsynchSndPlay(sndHandle);
  2831.         else
  2832.             theErr = HyperSndPlay(sndHandle);
  2833.     }
  2834.     if (theErr == noErr) {
  2835.         sndDoc->sndInUse = true;                // this document has a snd in use
  2836.         ShowStatusWindow(message);
  2837.     }
  2838.     else {
  2839.         KillSound();
  2840.         AlertUser(theErr, sSoundErr);
  2841.     }
  2842. }
  2843.  
  2844. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2845. /*
  2846. Given a sound resource ID, this will get my song resource and call the
  2847. SoundUnit to use that sampled sound and song to play a tune.  This
  2848. application will play one of two songs for sampled sounds.  If any error
  2849. is encountered, I call KillSound to dispose of all data.
  2850. */
  2851.  
  2852. #pragma segment Main
  2853. void PlaySndSong(SndDocPeek sndDoc, short sndID)
  2854. {
  2855.     SndChannelPtr    chan;
  2856.     SndListHandle    sndInst;
  2857.     SndListHandle    sndSong;
  2858.     OSErr            theErr;
  2859.  
  2860.     theErr = GetSelection(sndDoc, &sndInst);
  2861.     if (theErr == noErr) {
  2862.         sndSong = (SndListHandle)Get1Resource(soundListRsrc, sndID);
  2863.         theErr = ResError();                        // save any error
  2864.         if (sndSong != nil) {
  2865.             theErr = GetSampleChan(&chan, kInitNone, sndInst);
  2866.             if (theErr == noErr) {
  2867.                 sndDoc->sndInUse = true;            // this document has a snd in use
  2868.                 theErr = PlaySong(chan, sndSong);
  2869.             }
  2870.         }
  2871.     }
  2872.     if (theErr == noErr) {
  2873.         if (sndID == rScaleSnd)
  2874.             ShowStatusWindow(sScaleMsg);
  2875.         else                                        // I can play scales or a melody
  2876.             ShowStatusWindow(sMelodyMsg);
  2877.     }
  2878.     else {                                            // catch any errors
  2879.         KillSound();
  2880.         AlertUser(theErr, sSoundErr);
  2881.     }
  2882. }
  2883.  
  2884. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2885. /*
  2886. Given a sound resource ID, this will get my song resource and call the
  2887. SoundUnit to use the note synthesizer and song to play a tune.  I request
  2888. the note channel’s timbre (sounds like “tom burr”).  If any error is
  2889. encountered, I call KillSound to dispose of all data.
  2890. */
  2891.  
  2892. #pragma segment Main
  2893. void PlaySquareSong(short sndID)
  2894. {
  2895.  
  2896.     SndListHandle    sndSong;
  2897.     SndChannelPtr    chan;
  2898.     OSErr            theErr;
  2899.  
  2900.     sndSong = (SndListHandle)Get1Resource(soundListRsrc, sndID);
  2901.     if (sndSong != nil) {
  2902.         theErr = GetSquareWaveChan(&chan, kPreferredTimbre);
  2903.         if (theErr == noErr) {
  2904.             theErr = PlaySong(chan, sndSong);
  2905.             if (theErr == noErr) {
  2906.                 if (sndID == rScaleSnd)
  2907.                     ShowStatusWindow(sScaleMsg);
  2908.                 else                                // I play scales or a melody
  2909.                     ShowStatusWindow(sMelodyMsg);
  2910.             }
  2911.         }
  2912.         if (theErr != noErr) {                        // catch any errors
  2913.             KillSound();
  2914.             AlertUser(theErr, sSoundErr);
  2915.         }
  2916.     }
  2917.     else
  2918.         AlertUser(ResError(), sResErr);                // I’ll return the resource error
  2919. }
  2920.  
  2921. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2922. /*
  2923. This is a demonstration of the note synth’s tone qualities (or the lack
  2924. there of).  This simply loops through timbres sending alternating note and
  2925. rest commands.  Once all the notes have been sent, then I need to send a
  2926. callBackCmd to signal the SoundUnit to dispose of the channel.  Well,
  2927. actually the SoundUnit will set a global flag that the application will be
  2928. polling for later in the event loop.  Once this happens, or if any errors
  2929. are encountered along the way, KillSound will be called.  One very
  2930. disappointing aspect to changing the timbre is that the Mac Plus or SE
  2931. cannot handle this while a note is sounding.  The Apple Sound Chip can and
  2932. if you wanted to remove the rests try this routine on a Mac II, for
  2933. example, you can hear a continuous note while the timbre changes.  Try
  2934. this on a Mac Plus and you’ll hear garbage.  I’d show this myself, but how
  2935. do I determine if the Mac has the ASC?
  2936.  
  2937. BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
  2938. The note will continue to sound, looping forever, until a quietCmd is sent
  2939. or the channel is disposed of.  To prevent unwanted looping, I send a
  2940. quietCmd after all notes.
  2941. */
  2942.  
  2943. #pragma segment Main
  2944. void PlaySquareTimbres(void)
  2945. {
  2946.     SndChannelPtr    chan;
  2947.     short            timbre;
  2948.     OSErr            theErr;
  2949.  
  2950.     theErr = GetSquareWaveChan(&chan, kPreferredTimbre);
  2951.     if (theErr == noErr) {
  2952.         ShowStatusWindow(sTimbresMsg);
  2953.         for (timbre = kSineWave; timbre <= kSquareWave; timbre += 8) {
  2954.             theErr = SetSquareWaveTimbre(chan, timbre, kWait);
  2955.             if (theErr == noErr)
  2956.                 theErr = SendNote(chan, kOneSecond / 2, kOctave7 + Akey);
  2957.             if (theErr == noErr)
  2958.                 theErr = SendRest(chan, kOneSecond / 10);
  2959.             timbre = timbre + 8;                    // skip a few more timbres
  2960.             if (theErr != noErr)                    // if there was an error...
  2961.                 break;                                // get out of the loop
  2962.         }
  2963.     }
  2964.     if (theErr == noErr)
  2965.         theErr = SoundComplete(chan);
  2966.     else {                                            // catch any errors
  2967.         KillSound();
  2968.         AlertUser(theErr, sSoundErr);
  2969.     }
  2970. }
  2971.  
  2972. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2973. /*
  2974. This demonstrates the wave table synthesizers.  First thing to do is
  2975. obtain a wave cycle and the 'snd ' resource containing a song to be
  2976. played.  Then I’m ready to get the four wave channels and install the
  2977. chosen wave.  Once the wave is installed into the channel I can dispose of
  2978. the memory used to create the wave, since the Sound Manager will copy it
  2979. to its internal buffers.  At this point I’m ready to play the song.  It’s
  2980. not easy to hear four wave synths playing the same note with the same wave
  2981. table.  I was going to add a modifier to the channel that would transpose
  2982. each channels note, but I found a bug.
  2983.  
  2984. BUG NOTE: Installing a modifier to one of the wave table channels caused
  2985. the channel to fail.  My normal sequence of events is this: I synchronize
  2986. the four channels, send all the note commands, end this with the
  2987. callBackCmd, and finally release the channels to play their queues.  When
  2988. I added a modifier, the callBackCmd was the first command to be processed.
  2989. This caused my completion routines to be called and before the channel
  2990. made any sound it was disposed.  I tried this without the callBackCmd and
  2991. the channel never processed any commands.
  2992.  
  2993. BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound
  2994. chip based Mac.  At least not so far with any System 6.0x releases.  This
  2995. leaves the Mac Plus/SE without four tone polyphonic sound.
  2996.  
  2997. VERSION 1.2: No longer need to call HasWorkingWaveTables since Sound Manager
  2998. 2 and later fixed the problem.
  2999. */
  3000.  
  3001. #pragma segment Main
  3002. void PlayWaveScale(void)
  3003. {
  3004.     SndChannelPtr    chan1, chan2, chan3, chan4;
  3005.     SndListHandle    sndSong;
  3006.     SndListHandle    waveHandle;
  3007.     Ptr                waveTablePtr;
  3008.     long            offSet;
  3009.     short            sndType;
  3010.     short            waveLength;
  3011.     OSErr            theErr;
  3012.  
  3013.     sndSong = (SndListHandle)Get1Resource(soundListRsrc, rScaleSnd);
  3014.     if (sndSong != nil) {
  3015.         waveHandle = (SndListHandle)Get1Resource(soundListRsrc, rTenorVox);
  3016.         theErr = HoldSnd(waveHandle);
  3017.         if (theErr == noErr) {
  3018.             offSet = GetSndDataOffset(waveHandle, &sndType, &waveLength);
  3019.             waveTablePtr = (Ptr)((long)*waveHandle + offSet);
  3020.             theErr = GetWaveChans(&chan1, &chan2, &chan3, &chan4);
  3021.             if (theErr == noErr) {
  3022.                 theErr = InstallWave(chan1, waveTablePtr, waveLength);
  3023.                 if (theErr == noErr) {
  3024.                     theErr = InstallWave(chan2, waveTablePtr, waveLength);
  3025.                     if (theErr == noErr) {
  3026.                         theErr = InstallWave(chan3, waveTablePtr, waveLength);
  3027.                         if (theErr == noErr)
  3028.                             theErr = InstallWave(chan4, waveTablePtr, waveLength);
  3029.                     }
  3030.                 }
  3031.             }
  3032.             HUnlock((Handle)waveHandle);
  3033.             HPurge((Handle)waveHandle);
  3034.             if (theErr == noErr)
  3035.                 theErr = Play4ChanSongs(chan1, chan2, chan3, chan4,
  3036.                                         sndSong, sndSong, sndSong, sndSong);
  3037.             if (theErr == noErr)
  3038.                 ShowStatusWindow(sScaleMsg);
  3039.             else {                                // catch any SoundUnit errors
  3040.                 KillSound();
  3041.                 AlertUser(theErr, sSoundErr);
  3042.             }
  3043.         }
  3044.         else
  3045.             AlertUser(theErr, sResErr);            // couldn’t get waveHandle
  3046.     }
  3047.     else
  3048.         AlertUser(ResError(), sResErr);            // couldn’t get song
  3049. }
  3050.  
  3051. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3052. /*
  3053. This is a utility routine used to obtain the four snd resources that
  3054. contain parts of a song.  Nothing much to do other then get the resource
  3055. and do error checking.
  3056. */
  3057.  
  3058. #pragma segment Main
  3059. OSErr GetSndSongs(short sndSongID1, short sndSongID2,
  3060.                     short sndSongID3, short sndSongID4,
  3061.                     SndListHandle *sndSong1, SndListHandle *sndSong2,
  3062.                     SndListHandle *sndSong3, SndListHandle *sndSong4)
  3063. {
  3064.     OSErr    result = noErr;                            // initialize result
  3065.  
  3066.     // get all the snds
  3067.     *sndSong1 = (SndListHandle)Get1Resource(soundListRsrc, sndSongID1);
  3068.     if (sndSong1 != nil) {
  3069.         *sndSong2 = (SndListHandle)Get1Resource(soundListRsrc, sndSongID2);
  3070.         if (sndSong2 != nil) {
  3071.             *sndSong3 = (SndListHandle)Get1Resource(soundListRsrc, sndSongID3);
  3072.             if (sndSong3 != nil)
  3073.                 *sndSong4 = (SndListHandle)Get1Resource(soundListRsrc, sndSongID4);
  3074.         }
  3075.     }
  3076.     if ((sndSong1 == nil) || (sndSong2 == nil)
  3077.      || (sndSong3 == nil) || (sndSong4 == nil))
  3078.         result = ResError();                        // return resource error
  3079.     return (result);
  3080. }
  3081.  
  3082. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3083. /*
  3084. This routine is a utility I’m using to prepare four wave table channels.
  3085. First thing to do is obtain a wave table data I stored in a set of 'snd '
  3086. resources.  Then I’m ready to install these wave tables into the four wave
  3087. channels.  Once the waves are installed into the channel I can dispose of
  3088. the memory used to create each wave, since the Sound Manager will copy
  3089. them to its internal buffers.  At this point the channels are ready to
  3090. play sounds.
  3091. */
  3092.  
  3093. #pragma segment Main
  3094. OSErr InstallWaveSnds(SndChannelPtr chan1, SndChannelPtr chan2,
  3095.                       SndChannelPtr chan3, SndChannelPtr chan4,
  3096.                       short waveID1, short waveID2, short waveID3, short waveID4)
  3097. {
  3098.     SndListHandle    waveSnd1, waveSnd2, waveSnd3, waveSnd4;
  3099.     Ptr                wavePtr;
  3100.     long            offSet;
  3101.     short            waveLgth;
  3102.     short            sndType;
  3103.     OSErr            theErr;
  3104.  
  3105.     // get em and hold em down
  3106.     waveSnd1 = (SndListHandle)Get1Resource(soundListRsrc, waveID1);
  3107.     theErr = HoldSnd(waveSnd1);
  3108.     if (theErr == noErr) {
  3109.         waveSnd2 = (SndListHandle)Get1Resource(soundListRsrc, waveID2);
  3110.         theErr = HoldSnd(waveSnd2);
  3111.         if (theErr == noErr) {
  3112.             waveSnd3 = (SndListHandle)Get1Resource(soundListRsrc, waveID3);
  3113.             theErr = HoldSnd(waveSnd3);
  3114.             if (theErr == noErr) {
  3115.                 waveSnd4 = (SndListHandle)Get1Resource(soundListRsrc, waveID4);
  3116.                 theErr = HoldSnd(waveSnd4);
  3117.             }
  3118.         }
  3119.     }
  3120.     if (theErr != noErr)
  3121.         return (theErr);                            // we’re out of here
  3122.                                                     // catch the waves
  3123.     offSet = GetSndDataOffset(waveSnd1, &sndType, &waveLgth);
  3124.     wavePtr = (Ptr)((long)*waveSnd1 + offSet);
  3125.     theErr = InstallWave(chan1, wavePtr, waveLgth);
  3126.     if (theErr == noErr) {
  3127.         offSet = GetSndDataOffset(waveSnd2, &sndType, &waveLgth);
  3128.         wavePtr = (Ptr)((long)*waveSnd1 + offSet);
  3129.         theErr = InstallWave(chan2, wavePtr, waveLgth);
  3130.         if (theErr == noErr) {
  3131.             offSet = GetSndDataOffset(waveSnd3, &sndType, &waveLgth);
  3132.             wavePtr = (Ptr)((long)*waveSnd1 + offSet);
  3133.             theErr = InstallWave(chan3, wavePtr, waveLgth);
  3134.             if (theErr == noErr) {
  3135.                 offSet = GetSndDataOffset(waveSnd4, &sndType, &waveLgth);
  3136.                 wavePtr = (Ptr)((long)*waveSnd1 + offSet);
  3137.                 theErr = InstallWave(chan4, wavePtr, waveLgth);
  3138.             }
  3139.         }
  3140.     }
  3141.     HUnlock((Handle)waveSnd1);                        // done with resources
  3142.     HPurge((Handle)waveSnd1);
  3143.     HUnlock((Handle)waveSnd2);
  3144.     HPurge((Handle)waveSnd2);
  3145.     HUnlock((Handle)waveSnd3);
  3146.     HPurge((Handle)waveSnd3);
  3147.     HUnlock((Handle)waveSnd4);
  3148.     HPurge((Handle)waveSnd4);
  3149.     return (theErr);                                // return the error
  3150. }
  3151.  
  3152. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3153. /*
  3154. This is the demonstration of the wave table synthesizers.  This will get
  3155. four snd resources that contain parts to a song.  Then I get the four wave
  3156. table channels.  With these four channels, I install two other snd
  3157. resources that contain wave table data.  Once the four songs, four
  3158. channels, and two wave tables are ready then I play the song.
  3159.  
  3160. BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound
  3161. chip based Mac.  At least not so far with any System 6.0x release.  This
  3162. leaves the Mac Plus/SE without four tone polyphonic sound.
  3163.  
  3164. VERSION 1.2: No longer need to call HasWorkingWaveTables since Sound Manager
  3165. 2 and later fixed the problem.
  3166. */
  3167.  
  3168. #pragma segment Main
  3169. void PlayWaveMelody(void)
  3170. {
  3171.     SndListHandle    sndSong1, sndSong2, sndSong3, sndSong4;
  3172.     SndChannelPtr    chan1, chan2, chan3, chan4;
  3173.     OSErr            theErr;
  3174.  
  3175.     theErr = GetSndSongs(rMelodyPart1, rMelodyPart2, rMelodyPart3, rMelodyPart4,
  3176.                                 &sndSong1, &sndSong2, &sndSong3, &sndSong4);
  3177.     if (theErr != noErr) {
  3178.         AlertUser(theErr, sResErr);                // return the error
  3179.         return;                                    // we’re out of here
  3180.     }
  3181.     theErr = GetWaveChans(&chan1, &chan2, &chan3, &chan4);
  3182.     if (theErr == noErr) {
  3183.         theErr = InstallWaveSnds(chan1, chan2, chan3, chan4,
  3184.                             rWaveMelody, rWaveHarmony, rWaveHarmony, rWaveHarmony);
  3185.         if (theErr == noErr)
  3186.             theErr = Play4ChanSongs(chan1, chan2, chan3, chan4,
  3187.                                         sndSong1, sndSong2, sndSong3, sndSong4);
  3188.     }
  3189.     if (theErr == noErr)
  3190.         ShowStatusWindow(sMelodyMsg);
  3191.     else {                                        // catch any errors
  3192.         KillSound();
  3193.         AlertUser(theErr, sSoundErr);
  3194.     }
  3195. }
  3196.  
  3197. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3198. /*
  3199. This is the demonstration of the wave table synthesizers.  This will get
  3200. four snd resources that contain parts to a song.  Then I get the four wave
  3201. table channels.  With these four channels, I install four other snd resources
  3202. that contain wave table data.  Once the four songs, four channels, and
  3203. four wave tables are ready then I play the song.  By the way, SATB stands
  3204. for Soprano, Alto, Tenor, and Bass.  It’s standard music-speak.
  3205.  
  3206. BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound
  3207. chip based Mac.  At least not so far with any System 6.0x release.  This
  3208. leaves the Mac Plus/SE without four tone polyphonic sound.
  3209.  
  3210. VERSION 1.2: No longer need to call HasWorkingWaveTables since Sound Manager
  3211. 2 and later fixed the problem.
  3212. */
  3213.  
  3214. #pragma segment Main
  3215. void PlayWaveSATB(void)
  3216. {
  3217.     SndListHandle    sndSong1, sndSong2, sndSong3, sndSong4;
  3218.     SndChannelPtr    chan1, chan2, chan3, chan4;
  3219.     OSErr            theErr;
  3220.  
  3221.     theErr = GetSndSongs(rCounterPt1, rCounterPt2, rCounterPt3, rCounterPt4,
  3222.                                 &sndSong1, &sndSong2, &sndSong3, &sndSong4);
  3223.     if (theErr != noErr) {
  3224.         AlertUser(theErr, sResErr);                    // return the error
  3225.         return;                                        // we’re out of here
  3226.     }
  3227.     theErr = GetWaveChans(&chan1, &chan2, &chan3, &chan4);
  3228.     if (theErr == noErr) {
  3229.         theErr = InstallWaveSnds(chan1, chan2, chan3, chan4,
  3230.                                     rSopranoVox, rAltoVox, rTenorVox, rBassVox);
  3231.         if (theErr == noErr)
  3232.             theErr = Play4ChanSongs(chan1, chan2, chan3, chan4,
  3233.                                         sndSong1, sndSong2, sndSong3, sndSong4);
  3234.     }
  3235.     if (theErr == noErr)
  3236.         ShowStatusWindow(sCounterPtMsg);
  3237.     else {                                            // catch any errors
  3238.         KillSound();
  3239.         AlertUser(theErr, sSoundErr);
  3240.     }
  3241. }
  3242.  
  3243. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3244. /*
  3245. VERSION 1.2: Create four sample sound channels with an instrument sound
  3246. installed in each one. Then play the four part song to demonstrate the
  3247. sample sound channel.
  3248. */
  3249.  
  3250. #pragma segment Main
  3251. void PlaySampleSATB(void)
  3252. {
  3253.     SndListHandle    sndSong1, sndSong2, sndSong3, sndSong4;
  3254.     SndListHandle    sampleHarmony;
  3255.     SndChannelPtr    chan1, chan2, chan3, chan4;
  3256.     OSErr            theErr;
  3257.  
  3258.     theErr = GetSndSongs(rCounterPt1, rCounterPt2, rCounterPt3, rCounterPt4,
  3259.                                 &sndSong1, &sndSong2, &sndSong3, &sndSong4);
  3260.     if (theErr != noErr) {
  3261.         AlertUser(theErr, sResErr);                    // return the error
  3262.         return;                                        // we’re out of here
  3263.     }
  3264.  
  3265.     sampleHarmony = (SndListHandle)Get1Resource(soundListRsrc, rSampleHarmony);
  3266.     theErr = Get4SampleInstruments(&chan1, &chan2, &chan3, &chan4,
  3267.                             sampleHarmony, sampleHarmony, sampleHarmony, sampleHarmony);
  3268.     if (theErr == noErr)
  3269.         theErr = Play4ChanSongs(chan1, chan2, chan3, chan4,
  3270.                                 sndSong1, sndSong2, sndSong3, sndSong4);
  3271.     if (theErr == noErr)
  3272.         ShowStatusWindow(sCounterPtMsg);
  3273.     else                                            // catch any errors
  3274.     {
  3275.         KillSound();
  3276.         AlertUser(theErr, sSoundErr);
  3277.     }
  3278. }
  3279.  
  3280. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3281. /*
  3282. VERSION 1.2: Create four sample sound channels with an instrument sound
  3283. installed in each one. Then play the four part song to demonstrate the
  3284. sample sound channel.
  3285. */
  3286.  
  3287. #pragma segment Main
  3288. void PlaySampleMelody(void)
  3289. {
  3290.     SndListHandle    sndSong1, sndSong2, sndSong3, sndSong4;
  3291.     SndListHandle    sampleHarmony, sampleMelody;
  3292.     SndChannelPtr    chan1, chan2, chan3, chan4;
  3293.     OSErr            theErr;
  3294.  
  3295.     theErr = GetSndSongs(rMelodyPart1, rMelodyPart2, rMelodyPart3, rMelodyPart4,
  3296.                                 &sndSong1, &sndSong2, &sndSong3, &sndSong4);
  3297.     if (theErr != noErr) {
  3298.         AlertUser(theErr, sResErr);                    // return the error
  3299.         return;                                        // we’re out of here
  3300.     }
  3301.  
  3302.     sampleHarmony = (SndListHandle)Get1Resource(soundListRsrc, rSampleHarmony);
  3303.     sampleMelody = (SndListHandle)Get1Resource(soundListRsrc, rSampleMelody);
  3304.     theErr = Get4SampleInstruments(&chan1, &chan2, &chan3, &chan4,
  3305.                             sampleMelody, sampleHarmony, sampleHarmony, sampleHarmony);
  3306.     if (theErr == noErr)
  3307.         theErr = Play4ChanSongs(chan1, chan2, chan3, chan4,
  3308.                                 sndSong1, sndSong2, sndSong3, sndSong4);
  3309.     if (theErr == noErr)
  3310.         ShowStatusWindow(sMelodyMsg);
  3311.     else                                            // catch any errors
  3312.     {
  3313.         KillSound();
  3314.         AlertUser(theErr, sSoundErr);
  3315.     }
  3316. }
  3317.  
  3318. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3319. /*
  3320. VERSION 1.1:  This is just one of the reasons I hate the Dialog Manager.
  3321. I needed a simple dialog to ask the user for a name for their new sound.
  3322. To do this, I only needed a simple dialog with two buttons and an
  3323. editable text item.  Since the ModalDialog will not draw an outline
  3324. around the default item, I then needed an userItem.  This is the really
  3325. stupid part. The userItem is only there just to get a chance to draw an
  3326. outline around the default button.  It has no other purpose.  It is not
  3327. visible, not does it get any user interaction what so ever.  It's just a
  3328. pain in the ass.
  3329. */
  3330.  
  3331. #pragma segment Main
  3332. pascal void DefaultOutline(WindowPtr window, short theItem)
  3333. {
  3334. #pragma unused (theItem)
  3335.     Handle itemHndl;
  3336.     Rect itemRect;
  3337.     short kind;
  3338.  
  3339.     GetDialogItem((DialogPtr)window, ok, &kind, &itemHndl, &itemRect);
  3340.     DoButtonOutline((ControlHandle)itemHndl);
  3341. }
  3342.  
  3343. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3344. /*
  3345. VERSION 1.1:  Show a dialog asking the user to name their new sound
  3346. resource.  I need to outline the default button, so I have to install an
  3347. userItem setting its drawing procedure to do the outline around a
  3348. different item altogether.  The Dialog Manager really bugs me.
  3349. */
  3350.  
  3351. #pragma segment Main
  3352. void GetSndName(Str255 sndName)
  3353. {
  3354.     DialogPtr dialog;
  3355.     Handle itemHndl;
  3356.     Rect itemRect;
  3357.     short kind;
  3358.     short theItem;
  3359.  
  3360.     dialog = GetNewDialog(rGetNameDLOG, nil, (WindowPtr)-1);
  3361.     GetDialogItem(dialog, rUserItem, &kind, &itemHndl, &itemRect);
  3362.     SetDialogItem(dialog, rUserItem, kind, (Handle)DefaultOutline, &itemRect);
  3363.     do
  3364.         ModalDialog(nil, &theItem);
  3365.     while ((theItem != ok) && (theItem != cancel));
  3366.     if (theItem == ok) {
  3367.         GetDialogItem(dialog, rNameItem, &kind, &itemHndl, &itemRect);
  3368.         GetDialogItemText(itemHndl, sndName);
  3369.     } else
  3370.         sndName[0] = 0;
  3371.     DisposeDialog(dialog);
  3372. }
  3373.  
  3374. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3375. /*
  3376. VERSION 1.1:  This routine adds the given snd resource to the file and
  3377. document. First thing to do is find a proper resource ID for the 'snd '.
  3378. There is a reserved range for them, didn't you read the documentation?
  3379. Once this new resource is added then I update the file and re-build the
  3380. list of sounds.  This is necessary since the list must be in the same
  3381. order as the sounds are in the file.  I ignore the error returned by
  3382. InitSndList, since the chances of it failing at this point are slim.  If
  3383. anything is wrong with the list, then the rest of this application is
  3384. robust enough to handle a resource problem.  The user would have to close
  3385. the document and attempt to open it again.  If adding the resources
  3386. works, but updating the file fails then I will remove it.  This keeps the
  3387. document consistent with the file.
  3388. */
  3389.  
  3390. #pragma segment Main
  3391. OSErr AddSnd(SndDocPeek sndDoc, StringPtr sndNamePtr, SndListHandle sndHndl)
  3392. {
  3393.     short resID;
  3394.     OSErr theErr;
  3395.     short ignore;
  3396.  
  3397.     UseResFile(sndDoc->resFile);                //put our resource in the right file
  3398.     do
  3399.         resID = Unique1ID(soundListRsrc);
  3400.     while (resID < kSystemSndRange);
  3401.     AddResource((Handle)sndHndl, soundListRsrc, resID, sndNamePtr);
  3402.     theErr = ResError();
  3403.     UseResFile(gAppResRef);                        //restore our resource file
  3404.     if (theErr == noErr) {
  3405.         UpdateResFile(sndDoc->resFile);            //update the file
  3406.         theErr = ResError();
  3407.         if (theErr == noErr) {
  3408.             ignore = FlushVol(nil, sndDoc->vRefNum);
  3409.             LDelRow(0, 0, sndDoc->list);        //delete all the rows
  3410.             ignore = InitSndList(sndDoc);        //re-create the list
  3411.             SelectSndCell(sndDoc, resID);        //select the new snd
  3412.         } else
  3413.             RemoveResource((Handle)sndHndl);    //couldn't add it, update failed
  3414.     }
  3415.     return(theErr);
  3416. }
  3417.  
  3418. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3419. /*
  3420. VERSION 1.1:  This routine has one tricky aspect involving the Resource
  3421. Manager and removing a resource.  There's a catch.  I want the document
  3422. to always reflect the contents of the file on disk.  In other words,
  3423. InitSndList depends on UpdateResFile returning noErr.  If the file is
  3424. locked, then removing a resource shouldn't be allowed but RmveResource
  3425. will return noErr even if the file cannot be updated.  This is a
  3426. "feature" since the user may unlock the volume after removing the
  3427. resource and then UpdateResFile would work.  If RmveResource succeeds,
  3428. but UpdateResFile fails then the resource map in memory will be
  3429. inconsistent with what's on disk.  If the user then closed the document
  3430. and opened it again, the resource they just removed would magically still
  3431. be there.  The basic problem is that it is difficult to determine if a
  3432. given file will really allow write access.  There's at least three
  3433. situations to check: a locked file, a locked volume, or an AppleShare
  3434. folder privileges issue.  The ideal, and probably the best, solution
  3435. would be to deselect the edit commands from the user for a read-only
  3436. file.  This would involve lots of File Manager calls every time the user
  3437. selects a menu command.  (If you thought that GetFCBInfo would tell you
  3438. this, you're wrong.  You can have write permission to a file that is on a
  3439. locked volume.)  Additionally, the user needs to see that the reason the
  3440. edit menu doesn't work is because the file is read-only which would
  3441. require another feature to be added with lots more code.  This is why I
  3442. call ChangedResource before any other Resource Manager calls.
  3443. ChangedResource will determine if the resource can be written to disk.
  3444. If not, then it returns an error which is exactly what I wanted to know
  3445. in the first place.  I want to flush the cache to keep the disk's
  3446. resource map consistent with the resource data.  Otherwise, a crash could
  3447. occur the resource fork might be damaged and the file has to be repaired
  3448. (if possible) or deleted.  I'm ignoring the InitSndList result.  At this
  3449. stage, there's only a slim chance the list couldn't be re-built.  If it
  3450. does fail, the file should be closed.
  3451.  
  3452. A tip to the reader: dispose of as much memory as possible before calling
  3453. UpdateResFile.  This makes it faster.  UpdateResFile will not purge any
  3454. memory, but only attempts to use the available space.  If there's little
  3455. free space then UpdateResFile will run really slow.
  3456. */
  3457.  
  3458. #pragma segment Main
  3459. void ClearSnd(SndDocPeek sndDoc)
  3460. {
  3461.     SndListHandle    sndHndl;
  3462.     OSErr            theErr;
  3463.     short            ignore;
  3464.  
  3465.     theErr = GetSelection(sndDoc, &sndHndl);            //get the resource to remove
  3466.     if (theErr == noErr) {
  3467.         ChangedResource((Handle)sndHndl);                //can we change the file?
  3468.         theErr = ResError();                            //save the error result
  3469.         if (theErr == noErr) {
  3470.             UseResFile(sndDoc->resFile);                //use the right resource file
  3471.             RemoveResource((Handle)sndHndl);            //remove that sucker
  3472.             theErr = ResError();                        //save the error result
  3473.             UseResFile(gAppResRef);                        //restore our resource file
  3474.             if (theErr == noErr) {
  3475.                 DisposeHandle((Handle)sndHndl);            //get rid of the memory
  3476.                 UpdateResFile(sndDoc->resFile);            //update the file
  3477.                 theErr = ResError();                    //save the error result
  3478.                 if (theErr == noErr)
  3479.                     ignore = FlushVol(nil, sndDoc->vRefNum);
  3480.                 LDelRow(0, 0, sndDoc->list);            //delete all the rows
  3481.                 ignore = InitSndList(sndDoc);            //re-create the list
  3482.                 SelectNextCell(sndDoc->list, true);     //select the first cell
  3483.                 ActivateSndCntls(sndDoc);                //may not be any sounds left
  3484.             }
  3485.         }
  3486.     }
  3487.     if (theErr != noErr)
  3488.         AlertUser(theErr, sEditErr);
  3489. }
  3490.  
  3491. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3492. /*
  3493. VERSION 1.1:  This routine will create a handle and record sound into it.
  3494. A new sound will prompt the user for a name to add it to the document.
  3495. The Sound Input Manager will re-size this handle to be as small as
  3496. possible to contain only the samples recorded.  You can pass sndHandl ==
  3497. nil to SndRecord, which will cause the Sound Input Manager to create a
  3498. handle for you.  If adding the new sound is successful, the new resource
  3499. handle is marked purgeable, because I only use them temporally and they
  3500. tend to be large.  I have to set the sndHandle variable to nil in this
  3501. case since it is now belongs to the Resource Manager and I don't want to
  3502. dispose of it in the error handling code.  The choice of recording
  3503. quality wasn't given to the user.  Typically, users would only be
  3504. confused by the question of "what compression ratio do you prefer?"  As
  3505. a power user option, it would be nice to let the user set the rate.  Be
  3506. careful since this is a user interface issue, and Apple expects to see
  3507. lots of Sound Input features in applications.
  3508. */
  3509.  
  3510. #pragma segment Main
  3511. void DoRecordSound(SndDocPeek sndDoc)
  3512. {
  3513.     Str255            sndName;
  3514.     Point            recTopLeft;
  3515.     long            total;
  3516.     long            contig;
  3517.     SndListHandle    sndHandle;
  3518.     OSErr            theErr;
  3519.  
  3520.     KillSound();
  3521.     PurgeSpace(&total, &contig);
  3522.     sndHandle = (SndListHandle)NewHandle(contig - kMinSpace);
  3523.     if (sndHandle != nil) {
  3524.         recTopLeft.v = kRecordTop;
  3525.         recTopLeft.h = kRecordLeft;
  3526.         theErr = SndRecord(nil, recTopLeft, siBestQuality, &sndHandle);
  3527.         if (theErr == noErr) {
  3528.             GetSndName(sndName);
  3529.             theErr = AddSnd(sndDoc, sndName, sndHandle);
  3530.             if (theErr == noErr) {
  3531.                 HPurge((Handle)sndHandle);
  3532.                 sndHandle = nil;
  3533.             }
  3534.         }
  3535.     } else
  3536.         theErr = MemError();
  3537.     if (sndHandle != nil)
  3538.         DisposeHandle((Handle)sndHandle);
  3539.     if ((theErr != noErr) && (theErr != userCanceledErr))
  3540.         AlertUser(theErr, sSoundErr);
  3541. }
  3542.  
  3543. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3544. /*
  3545. Enable and disable menus based on the current state.  The user can only
  3546. select enabled menu items so I set up all the menu items before calling
  3547. MenuSelect or MenuKey, since these are the only times that a menu item
  3548. can be selected. Note that MenuSelect is also the only time the user will
  3549. see menu items. This approach to deciding what enable/disable state a menu
  3550. item has the advantage of concentrating all the decision making in one
  3551. routine, as opposed to being spread throughout the application.  Other
  3552. application designs may take a different approach that may or may not be
  3553. just as valid.
  3554. */
  3555.  
  3556. #pragma segment Main
  3557. void AdjustMenus(void)
  3558. {
  3559.      MenuHandle menu;
  3560.     WindowPtr window;
  3561.     Boolean allowEdit;
  3562.  
  3563.     window = FrontWindow();
  3564.     menu = GetMenuHandle(mFile);                    //the File menu and items
  3565.     if (IsDAWindow(window) || IsDocWindow(window))
  3566.         EnableItem(menu, iClose);
  3567.     else
  3568.         DisableItem(menu, iClose);
  3569.  
  3570.     menu = GetMenuHandle(mEdit);                    //the Edit menu and items
  3571.     allowEdit = IsDAWindow(window);
  3572.     if (allowEdit)                                    //check the undo item
  3573.         EnableItem(menu, iUndo);
  3574.     else
  3575.         DisableItem(menu, iUndo);
  3576.  
  3577.     if (IsDocWindow(window)) {                        //handle the other edit items
  3578.         allowEdit = HasSelection((SndDocPeek)window) || allowEdit;
  3579.         EnableItem(menu, iPaste);
  3580.     } else
  3581.         DisableItem(menu, iPaste);
  3582.  
  3583.     if (allowEdit) {
  3584.         EnableItem(menu, iCut);
  3585.         EnableItem(menu, iCopy);
  3586.         EnableItem(menu, iClear);
  3587.     } else {
  3588.         DisableItem(menu, iCut);
  3589.         DisableItem(menu, iCopy);
  3590.         DisableItem(menu, iClear);
  3591.     }
  3592. }
  3593.  
  3594. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3595. /*
  3596. VERSION 1.1:  Get the currently selected item and copy it to the
  3597. clipboard.  I want to also save the name for this resource, so I add a
  3598. string type to the clipboard as well.
  3599. */
  3600.  
  3601. #pragma segment Main
  3602. void CopySnd(SndDocPeek sndDoc)
  3603. {
  3604.     Str255            sndName;
  3605.     ResType            rType;
  3606.     SndListHandle    sndHndl;
  3607.     long            scrapLen;
  3608.     short            id;
  3609.     OSErr            theErr;
  3610.  
  3611.     theErr = GetSelection(sndDoc, &sndHndl);
  3612.     if (theErr == noErr) {
  3613.         GetResInfo((Handle)sndHndl, &id, &rType, sndName);
  3614.         scrapLen = ZeroScrap();                        //ignoring the result
  3615.         theErr = PutScrap(StrLength(sndName) + 1, 'STR ', sndName);
  3616.         HLock((Handle)sndHndl);
  3617.         theErr = PutScrap(GetHandleSize((Handle)sndHndl), soundListRsrc, (Ptr)(*sndHndl));
  3618.         HUnlock((Handle)sndHndl);
  3619.         HPurge((Handle)sndHndl);
  3620.     }
  3621.     if (theErr != noErr)
  3622.         AlertUser(theErr, sEditErr);
  3623. }
  3624.  
  3625. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3626. /*
  3627. VERSION 1.1:  Copy the selection and then delete it.
  3628. */
  3629.  
  3630. #pragma segment Main
  3631. void CutSnd(SndDocPeek sndDoc)
  3632. {
  3633.     CopySnd(sndDoc);
  3634.     ClearSnd(sndDoc);
  3635. }
  3636.  
  3637. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3638. /*
  3639. VERSION 1.1:  This routine will create a new resource into the file
  3640. containing the clipboard's sound data.  A name is attempted to be found
  3641. by first checking for the string type in the scrap.  This application
  3642. will always put both a string and a sound together on the clipboard.  But
  3643. if the user copied a sound from the Sound cdev, ResEdit, or by some other
  3644. method then I'll ask the user for a new name.  If adding the new resource
  3645. is successful, then I mark the resource as purgeable and set the sndHndl
  3646. variable to NIL.  The Resource Manager then owns the handle and I don't
  3647. want my error handling to dispose of this new handle.
  3648. */
  3649.  
  3650. #pragma segment Main
  3651. void PasteSnd(SndDocPeek sndDoc)
  3652. {
  3653.     Str255            sndName;
  3654.     StringHandle    sndNameHndl;
  3655.     long            offset;
  3656.     long            scrapLen;
  3657.     SndListHandle    sndHndl;
  3658.     OSErr            theErr;
  3659.  
  3660.     sndHndl = (SndListHandle)NewHandle(0);
  3661.     sndNameHndl = NewString("\p");
  3662.     theErr = MemError();
  3663.     scrapLen = GetScrap((Handle)sndNameHndl, 'STR ', &offset);
  3664.     scrapLen = GetScrap((Handle)sndHndl, soundListRsrc, &offset);
  3665.     if (scrapLen > 0) {
  3666.         if (sndNameHndl != nil)
  3667.             PStringCopy(*sndNameHndl, sndName);
  3668.         else
  3669.             sndName[0] = 0;
  3670.         if (StrLength(sndName) == 0)
  3671.             GetSndName(sndName);
  3672.         theErr = AddSnd(sndDoc, sndName, sndHndl);
  3673.         if (theErr == noErr) {
  3674.             HPurge((Handle)sndHndl);
  3675.             sndHndl = nil;                                        //done with handle
  3676.             ActivateSndCntls(sndDoc);
  3677.         }
  3678.     } else
  3679.         theErr = scrapLen;
  3680.  
  3681.     if (sndHndl != nil)
  3682.         DisposeHandle((Handle)sndHndl);
  3683.     if (sndNameHndl != nil)
  3684.         DisposeHandle((Handle)sndNameHndl);
  3685.     if (theErr != noErr)
  3686.         AlertUser(theErr, sEditErr);
  3687. }
  3688.  
  3689. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3690. /*
  3691. This is called when an item is chosen from the menu bar (after calling
  3692. MenuSelect or MenuKey). It performs the right operation for each command.
  3693. It is good to have both the result of MenuSelect and MenuKey go to
  3694. one routine like this to keep everything organized.
  3695.  
  3696. VERSION 1.1:  Supporting the edit commands for snd resources.
  3697. */
  3698.  
  3699. #pragma segment Main
  3700. void DoMenuCommand(long menuResult)
  3701. {
  3702.     WindowPtr window;
  3703.     short menuID;
  3704.     short menuItem;                            //resource ID and item of the selected menu
  3705.     short daRefNum;
  3706.     Str255 daName;
  3707.     Boolean ignore;
  3708.  
  3709.     menuID = HighWord(menuResult);            //use macros for efficiency...
  3710.     menuItem = LowWord(menuResult);            //to get menu item number and menu number
  3711.     switch (menuID) {
  3712.  
  3713.         case mApple:
  3714.             switch (menuItem) {
  3715.                 case iAbout:                //bring up alert for About
  3716.                     DoAbout();
  3717.                     break;
  3718.                 default:                    //all non-About items in this menu are DAs
  3719.                     GetMenuItemText(GetMenuHandle(mApple), menuItem, daName);
  3720.                     daRefNum = OpenDeskAcc(daName);
  3721.                     break;
  3722.             }
  3723.             break;
  3724.  
  3725.         case mFile:
  3726.             switch (menuItem) {
  3727.                 case iNew:
  3728.                     NewSoundDoc();
  3729.                     break;
  3730.                 case iOpen:
  3731.                     GetSoundDoc();
  3732.                     break;
  3733.                 case iClose:
  3734.                     ignore = DoCloseWindow(FrontWindow()); //I don’t care if cancelled
  3735.                     break;
  3736.                 case iQuit:
  3737.                     Terminate();
  3738.                     break;
  3739.             }
  3740.             break;
  3741.  
  3742.         case mEdit:                                //call SystemEdit for DA editing && MultiFinder
  3743.             if (! SystemEdit(menuItem - 1)) {    //since I don’t do any editing
  3744.                 window = FrontWindow();
  3745.                 if (IsDocWindow(window)) {
  3746.                     switch (menuItem) {
  3747.  
  3748.                         case iCut:
  3749.                             CutSnd((SndDocPeek)window);
  3750.                             break;
  3751.                         case iCopy:
  3752.                             CopySnd((SndDocPeek)window);
  3753.                             break;
  3754.                         case iPaste:
  3755.                             PasteSnd((SndDocPeek)window);
  3756.                             break;
  3757.                         case iClear:
  3758.                             ClearSnd((SndDocPeek)window);
  3759.                             break;
  3760.                     }
  3761.                 }
  3762.             }
  3763.             break;
  3764.  
  3765.         case mDemos:
  3766.             switch (menuItem) {
  3767.  
  3768.                 case iCheckVolume:
  3769.                     CheckSoundVolume();
  3770.                     break;
  3771.  
  3772.                 case iSquareScale:
  3773.                     PlaySquareSong(rScaleSnd);
  3774.                     break;
  3775.  
  3776.                 case iSquareMelody:
  3777.                     PlaySquareSong(rMelodyPart1);
  3778.                     break;
  3779.  
  3780.                 case iSquareTimbre:
  3781.                     PlaySquareTimbres();
  3782.                     break;
  3783.  
  3784.                 case iWaveScale:
  3785.                     PlayWaveScale();
  3786.                     break;
  3787.  
  3788.                 case iWaveMelody:
  3789.                     PlayWaveMelody();
  3790.                     break;
  3791.  
  3792.                 case iWaveSATB:
  3793.                     PlayWaveSATB();
  3794.                     break;
  3795.  
  3796.                 case iSampleMelody:
  3797.                     PlaySampleMelody();
  3798.                     break;
  3799.  
  3800.                 case iSampleSATB:
  3801.                     PlaySampleSATB();
  3802.                     break;
  3803.  
  3804.             } //switch (menuItem)
  3805.  
  3806.     } //switch (menuID)
  3807.     HiliteMenu(0);                        //unhighlight what MenuSelect or MenuKey hilited
  3808. }
  3809.  
  3810. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3811. /*
  3812. Draw the contents of the application window in response to an update
  3813. event.  At this point, BeginUpdate has been called which sets the window’s
  3814. visRgn to clip drawing only where it needs to be done.  I have the
  3815. controls to draw, a list to update, and a default button to outline.  I
  3816. use UpdateControls to avoid needless drawing that happens with DrawControls.
  3817. It not only runs faster but doesn’t flicker.
  3818. */
  3819.  
  3820. #pragma segment Main
  3821. void DrawSndWindow(WindowPtr window)
  3822. {
  3823.     ControlHandle control;
  3824.     Rect theRect;
  3825.  
  3826.     PenNormal();
  3827.     LUpdate(window->visRgn, ((SndDocPeek)window)->list);    // update list
  3828.     theRect = (**((SndDocPeek)window)->list).rView;            // frame the list
  3829.     theRect.right = theRect.right + kScrollbarAdjust;
  3830.     InsetRect(&theRect, kListFrameInset, kListFrameInset);
  3831.     FrameRect(&theRect);
  3832.     UpdateControls(window, window->visRgn);                    // update controls
  3833.     control = ((WindowPeek)window)->controlList;            // draw button outline
  3834.     while (control != nil) {
  3835.         if (GetControlReference(control) == rPlaySndCntl)
  3836.             DoButtonOutline(control);
  3837.         control = (**control).nextControl;
  3838.     }
  3839. }
  3840.  
  3841. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3842. /*
  3843. When the user types a key, I check if it is one that I’m looking for.
  3844. This routine only handles the non-command key events.  Here I’m looking
  3845. for a arrow key or the return and enter keys for the default button.
  3846.  
  3847. VERSION 1.1: The enter and return keys only work if there is a selection.
  3848. Now supporting the backspace and delete keys which clear the selection.
  3849. */
  3850.  
  3851. #pragma segment Main
  3852. void DoKeyDown(char key, WindowPtr window)
  3853. {
  3854.     ControlHandle control;
  3855.     Boolean ignore;
  3856.  
  3857.     switch (GetWRefCon(window)) {
  3858.  
  3859.         case rSoundWindow:
  3860.             switch (key) {
  3861.  
  3862.                 case kEnterKey:
  3863.                 case kReturnKey:
  3864.                     if (HasSelection((SndDocPeek)window)) {
  3865.                         control = ((WindowPeek)window)->controlList;
  3866.                         while (control != nil) {            //find the default button
  3867.                             if (GetControlReference(control) == rPlaySndCntl)
  3868.                                 SelectButton(control);            //here it is
  3869.                             control = (**control).nextControl;
  3870.                         }
  3871.                         PlaySelectedSnd((SndDocPeek)window, sPlayingMsg);
  3872.                     }
  3873.                     break;
  3874.  
  3875.                 case kUpArrow:
  3876.                     SelectNextCell(((SndDocPeek)window)->list, false);
  3877.                     ActivateSndCntls((SndDocPeek)window);
  3878.                     break;
  3879.  
  3880.                 case kDownArrow:
  3881.                     SelectNextCell(((SndDocPeek)window)->list, true);
  3882.                     ActivateSndCntls((SndDocPeek)window);
  3883.                     break;
  3884.  
  3885.                 case kBackspace:
  3886.                     ClearSnd((SndDocPeek)window);
  3887.                     break;
  3888.             }
  3889.             break; //rSoundWindow
  3890.  
  3891.         case rStatusWindow:
  3892.             if (    (key == kEnterKey)
  3893.                  || (key == kReturnKey)
  3894.                  || (key == kEscape) ) {
  3895.                 SelectButton(((WindowPeek)window)->controlList);
  3896.                 KillSound();
  3897.             }
  3898.             break;
  3899.  
  3900.         case rAboutWindow:
  3901.             if (    (key == kEnterKey)
  3902.                  || (key == kReturnKey)
  3903.                  || (key == kEscape) ) {
  3904.                 SelectButton(((WindowPeek)window)->controlList);
  3905.                 ignore = DoCloseWindow(window);
  3906.             }
  3907.             break;
  3908.     } //switch (GetWRefCon(window))
  3909. }
  3910.  
  3911. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3912. /*
  3913. Given a document, this will handle any clicking in the list.  The mouse
  3914. point (event.where) is expected to have been adjusted to the local
  3915. coordinates of the window.  If this routine does handle the event, then
  3916. I return true.
  3917.  
  3918. BUG NOTE: LClick will return a double-click even if no cell was selected.
  3919. So I have to test for the last click being in a real cell.  If not, then
  3920. I will not process the double click.  Also, there is another bug in the
  3921. List Manager.  It will not always deselect the currently selected cell when
  3922. the user clicks outside of the data bounds.  In other words, sometimes
  3923. my list would show 4 items when the list has room to show 8.  If the user
  3924. clicked in the bottom area of the list (below the last item) the List
  3925. Manager should deselect any items.  It doesn’t all the time, just sometimes.
  3926. The only real solution would be to write a new LClick.
  3927. */
  3928.  
  3929. #pragma segment Main
  3930. Boolean ListClick(SndDocPeek sndDoc, EventRecord *event)
  3931. {
  3932.     Cell aCell;
  3933.     Rect listRect;
  3934.     Boolean result;
  3935.  
  3936.     listRect = (**(sndDoc->list)).rView;
  3937.     listRect.right = listRect.right + kScrollbarAdjust;
  3938.     if (PtInRect(event->where, &listRect)) {
  3939.         if (LClick(event->where, event->modifiers, sndDoc->list)) {
  3940.             aCell = LLastClick(sndDoc->list);
  3941.             if ( PtInRect(aCell, &((**(sndDoc->list)).dataBounds)) )
  3942.                 PlaySelectedSnd(sndDoc, sPlayingMsg);
  3943.         }
  3944.         ActivateSndCntls(sndDoc);
  3945.         result = true;                                    //I handled the event
  3946.     } else
  3947.         result = false;
  3948.     return(result);
  3949. }
  3950.  
  3951. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3952. /*
  3953. This is called when a mouse-down event occurs in the content of my
  3954. application windows.  First thing to check for is a click in the list.
  3955. If not a list click, then find what control may have been clicked in and
  3956. handle that.
  3957.  
  3958. VERSION 1.1: Handle the record button.
  3959. */
  3960.  
  3961. #pragma segment Main
  3962. void DoSndDocClick(SndDocPeek sndDoc, EventRecord *event)
  3963. {
  3964.     ControlHandle control;
  3965.  
  3966.     SetPort((GrafPtr)sndDoc);
  3967.     GlobalToLocal(&event->where);
  3968.     if (! ListClick(sndDoc, event)) {
  3969.         if (FindControl(event->where, (WindowPtr)sndDoc, &control) != 0) {
  3970.             if (TrackControl(control, event->where, nil) != 0) {
  3971.                 switch (GetControlReference(control)) {
  3972.  
  3973.                     case rPlaySndCntl:
  3974.                         PlaySelectedSnd(sndDoc, sPlayingMsg);
  3975.                         break;
  3976.  
  3977.                     case rHyperPlayCntl:
  3978.                         PlaySelectedSnd(sndDoc, sHyperMsg);
  3979.                         break;
  3980.  
  3981.                     case rPlayScaleCntl:
  3982.                         PlaySndSong(sndDoc, rScaleSnd);
  3983.                         break;
  3984.  
  3985.                     case rMelodyCntl:
  3986.                         PlaySndSong(sndDoc, rMelodyPart1);
  3987.                         break;
  3988.  
  3989.                     case rStopCntl:
  3990.                         KillSound();
  3991.                         break;
  3992.  
  3993.                     case rRecordCntl:
  3994.                         DoRecordSound(sndDoc);
  3995.                         break;
  3996.  
  3997.                 } //switch GetControlReference(control)
  3998.             } //if TrackControl
  3999.         } //if FindControl
  4000.     } //if ! ListSelect
  4001. }
  4002.  
  4003. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4004. /*
  4005. The status window has a stop button.  This will stop any sound in
  4006. progress.  So, if the user clicks in my status window I need to check
  4007. for this.
  4008. */
  4009.  
  4010. #pragma segment Main
  4011. void DoStatClick(StatWindowPeek statWindow, EventRecord *event)
  4012. {
  4013.     ControlHandle control;
  4014.  
  4015.     SetPort((GrafPtr)statWindow);
  4016.     GlobalToLocal(&(event->where));
  4017.     if (FindControl(event->where, (WindowPtr)statWindow, &control) != 0)
  4018.         if (TrackControl(control, event->where, nil) != 0)
  4019.             KillSound();
  4020. }
  4021.  
  4022. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4023. /*
  4024. The about window has a single OK button.  If the users clicks in it, then
  4025. close the window.
  4026. */
  4027.  
  4028. #pragma segment Main
  4029. void DoAboutClick(WindowPtr window, EventRecord *event)
  4030. {
  4031.     ControlHandle control;
  4032.     Boolean ignore;
  4033.  
  4034.     SetPort(window);
  4035.     GlobalToLocal(&event->where);
  4036.     if (FindControl(event->where, (WindowPtr)window, &control) != 0) {
  4037.         if (TrackControl(control, event->where, nil) != 0)
  4038.             ignore = DoCloseWindow(window);
  4039.     }
  4040. }
  4041.  
  4042. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4043. /*
  4044. This is called when an update event is received for any of my windows.  It
  4045. calls the appropriate window’s update routine to draw its contents.  As an
  4046. efficiency measure that does not have to be followed, it calls the drawing
  4047. routine only if the visRgn is non-empty.  This will handle situations
  4048. where calculations for drawing or drawing itself is very time-consuming.
  4049. Why does QD give you an update with an empty updateRgn?
  4050. */
  4051.  
  4052. #pragma segment Main
  4053. void DoUpdate(WindowPtr window)
  4054. {
  4055.     BeginUpdate(window);                            //setup the visRgn, clears updateRgn
  4056.     if (! EmptyRgn(window->visRgn)) {                 //if updating to be done
  4057.         SetPort(window);                            //set to the current port
  4058.         switch (GetWRefCon(window)) {                //call the window’s drawing routine
  4059.  
  4060.             case rSoundWindow:
  4061.                 DrawSndWindow(window);
  4062.                 break;
  4063.  
  4064.             case rStatusWindow:
  4065.                 DrawStatusWindow();
  4066.                 break;
  4067.  
  4068.             case rAboutWindow:
  4069.                 DrawAboutWindow(window);
  4070.                 break;
  4071.         }
  4072.     }
  4073.     EndUpdate(window);                                //restores the visRgn
  4074. }
  4075.  
  4076. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4077. /*
  4078. This is called when a window is to be activated or deactivated.  For the
  4079. document window I activate all the controls and the list.  For the status
  4080. window I activate the one and only control.  This is also called for
  4081. suspend and resume events while running MultiFinder.
  4082. */
  4083.  
  4084. #pragma segment Main
  4085. void DoActivate(WindowPtr window, Boolean becomingActive)
  4086. {
  4087.     if (window != nil) {
  4088.         switch (GetWRefCon(window)) {
  4089.  
  4090.             case rSoundWindow:
  4091.                 ActivateSndCntls((SndDocPeek)window);
  4092.                 LActivate(becomingActive, ((SndDocPeek)window)->list);
  4093.                 break;
  4094.  
  4095.             case rStatusWindow:
  4096.                 if (becomingActive)                            //it only has one control
  4097.                     HiliteControl(((WindowPeek)window)->controlList, kNoHiliteControlPart);
  4098.                 else
  4099.                     HiliteControl(((WindowPeek)window)->controlList, kControlInactiveControlPart);
  4100.                 DoButtonOutline(((WindowPeek)window)->controlList);
  4101.                 break;
  4102.  
  4103.             case rAboutWindow:
  4104.                 if (becomingActive)                            //it only has one control
  4105.                     HiliteControl(((WindowPeek)window)->controlList, kNoHiliteControlPart);
  4106.                 else
  4107.                     HiliteControl(((WindowPeek)window)->controlList, kControlInactiveControlPart);
  4108.                 DoButtonOutline(((WindowPeek)window)->controlList);
  4109.                 break;
  4110.         }
  4111.     }
  4112. }
  4113.  
  4114. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4115. /*
  4116. The required AppleEvent sent to tell an application to quit. Nothing much to do.
  4117. Just call our terminate routine to clean up and then exit through the event loop.
  4118. Note that you cannot exit from here, the system will crash. You must return to
  4119. the AppleEvent Manager. Do not call ExitToShell at this point!
  4120. */
  4121.  
  4122. #pragma segment Main
  4123. pascal OSErr QuitApplicationEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  4124. {
  4125. #pragma unused (theAppleEvent, reply, handlerRefcon)
  4126.  
  4127.     Terminate();
  4128.     return(noErr);
  4129. }
  4130.  
  4131. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4132. /*
  4133. One of the required AppleEvents. This is a request to open a document. Find
  4134. the document being requested, and open it.
  4135. */
  4136.  
  4137. #pragma segment Main
  4138. pascal OSErr OpenDocumentsEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  4139. {
  4140. #pragma unused (reply, handlerRefcon)
  4141.  
  4142.     FSSpec                file;
  4143.     AEDescList            docList;
  4144.     long                i;
  4145.     long                itemsInList;
  4146.     Size                actualSize;
  4147.     AEKeyword            keyword;
  4148.     DescType            returnedType;
  4149.     OSErr                theErr;
  4150.  
  4151.     // get the direct parameter--a descriptor list--and put it into docList
  4152.     theErr = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList, &docList);
  4153.     if (theErr == noErr)
  4154.     {
  4155.         if (docList.descriptorType != typeAEList)
  4156.         {
  4157.             AEDisposeDesc (&docList);
  4158.             theErr = paramErr;
  4159.         }
  4160.         else
  4161.         {
  4162.             // count the number of descriptor records in the list
  4163.             theErr = AECountItems(&docList, &itemsInList);
  4164.             if (theErr == noErr)
  4165.             {
  4166.                 //get each descriptor record from the list, coerce the returned data
  4167.                 //to an FSSpec and open the associated file
  4168.  
  4169.                 for (i = 1; i <= itemsInList; i++)
  4170.                 {
  4171.                     theErr = AEGetNthPtr(&docList, i, typeFSS, &keyword, &returnedType,
  4172.                                             &file, sizeof(file), &actualSize);
  4173.                     if (theErr == noErr)
  4174.                         OpenSoundDoc(&file);
  4175.                 }
  4176.             }
  4177.             AEDisposeDesc (&docList);
  4178.         }
  4179.     }
  4180.     return (theErr);
  4181. }
  4182.  
  4183. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4184. /*
  4185. It's a required AppleEvent and we don't use it.
  4186. */
  4187.  
  4188. #pragma segment Main
  4189. pascal OSErr PrintDocumentsEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  4190. {
  4191. #pragma unused (theAppleEvent, reply, handlerRefcon)
  4192.  
  4193.     return (errAEEventNotHandled);
  4194. }
  4195.  
  4196. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4197. /*
  4198. It's a required AppleEvent and we don't use it.
  4199. */
  4200.  
  4201. #pragma segment Main
  4202. pascal OSErr OpenApplicationEvent(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  4203. {
  4204. #pragma unused (theAppleEvent, reply, handlerRefcon)
  4205.  
  4206.     return (noErr);
  4207. }
  4208.  
  4209. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4210. /*
  4211. As Spike Lee says, “Do the right thing.” Determine what kind of event it
  4212. is, and call the appropriate routines.  Key down events are first tested
  4213. as being command key events for menus or command-. for cancel.  If it’s
  4214. the command-. the user is asking to cancel the sound.  In this case, if
  4215. the front window is the status window then the button should look as if it
  4216. has been clicked.  This is proper human interface.  Any non-menu command
  4217. keys are passed to DoKeyDown.  I have a global flag, gInModalState, which
  4218. is used to handle the front window as a modal dialog.  This means I ignore
  4219. clicks outside of the modal window and menu command keys.  I could have a
  4220. different global flag that would mean it’s a modal window but uses menu
  4221. commands, such as gInModalMenuState.  An important note is that I do pass
  4222. any other non-menu command keys to DoKeys, so the modal window could have
  4223. access to key events.  One last thing to note happens during a suspend
  4224. event.   Applications using sound should dispose of their sound channels
  4225. at suspend time.  No other application can use sound while another one has
  4226. channels allocated.  To stop my sound, I call KillSound which hides the
  4227. status window.  MultiFinder does properly deactivate the front window at
  4228. suspend time and applications normally do not have to worry about
  4229. HiliteWindow.  I do because the front window could have been the status
  4230. window and I’ve removed it after getting a suspend event.  This causes the
  4231. next window, a document window, to become highlighted as the active
  4232. window.  To avoid my next window from being highlighted while in the
  4233. background, I have to call HiliteWindow *after* KillSound.  This is
  4234. strange and only applications changing the front window at suspend event
  4235. time have to be concerned about this.
  4236.  
  4237. VERSION 1.1: Added a constant, kSFTopLeft, to specify the dialog position.
  4238. I do not call KillSound during a MultiFinder switch unless we're running
  4239. the older Sound Manager.  The new one allows for multiple sound
  4240. channels or will return the proper error otherwise.
  4241. */
  4242.  
  4243. #pragma segment Main
  4244. void DoEvent(EventRecord *event)
  4245. {
  4246.     WindowPtr window;
  4247.     Point where;
  4248.     short part;
  4249.     short err;
  4250.     Boolean ignore;
  4251.     char key;
  4252.  
  4253.     switch (event->what) {
  4254.  
  4255.         case mouseDown:
  4256.             part = FindWindow(event->where, &window);
  4257.             if (    (IsModalWindow(FrontWindow()) && (window != FrontWindow()))
  4258.                  || (IsModalWindow(FrontWindow()) && (part == inMenuBar)))
  4259.             {
  4260.                 SysBeep(30);                            //click outside of modal window
  4261.                 return;                                    //break out of routine
  4262.             }
  4263.             switch (part) {
  4264.                 case inMenuBar:                                //process the menu command
  4265.                     AdjustMenus();
  4266.                     DoMenuCommand(MenuSelect(event->where));
  4267.                     break;
  4268.  
  4269.                 case inSysWindow:                            //let system handle the mouseDown
  4270.                     SystemClick(event, window);
  4271.                     break;
  4272.  
  4273.                 case inContent:
  4274.                     if ((window != FrontWindow()))
  4275.                         SelectWindow(window);
  4276.                     else {
  4277.                         switch (GetWRefCon(window)) {
  4278.  
  4279.                             case rSoundWindow:
  4280.                                 DoSndDocClick((SndDocPeek)window, event);
  4281.                                 break;
  4282.  
  4283.                             case rStatusWindow:
  4284.                                 DoStatClick((StatWindowPeek)window, event);
  4285.                                 break;
  4286.  
  4287.                             case rAboutWindow:
  4288.                                 DoAboutClick(window, event);
  4289.                                 break;
  4290.  
  4291.                             } //switch (GetWRefCon(window))
  4292.                         }
  4293.                     break; //inContent
  4294.  
  4295.                 case inDrag:                        //pass screenBits.bounds to get all gDevices
  4296.                     DragWindow(window, event->where, &qd.screenBits.bounds);
  4297.                     break;
  4298.  
  4299.                 case inGoAway:
  4300.                     if (TrackGoAway(window, event->where))
  4301.                         ignore = DoCloseWindow(window);
  4302.                     break;
  4303.  
  4304.             } //switch (part)
  4305.             break; //mouseDown
  4306.  
  4307.         case keyDown:
  4308.         case autoKey:
  4309.             window = FrontWindow();
  4310.             key = event->message & charCodeMask;
  4311.             if ((event->modifiers & cmdKey) != 0)            //Command key down?
  4312.             {
  4313.                 if (key == kPeriod)
  4314.                 {
  4315.                     if (window == (WindowPtr)gStatusWindow)
  4316.                         SelectButton(((WindowPeek)gStatusWindow)->controlList);
  4317.                     KillSound();
  4318.                 } else
  4319.                     if ((event->what == keyDown) && (! IsModalWindow(window)))
  4320.                     {
  4321.                         AdjustMenus();                        //adjust items properly
  4322.                         DoMenuCommand(MenuKey(key));
  4323.                     }
  4324.             } else                                            //non-Command keys
  4325.                 if (window != nil)                            //if there’s a window
  4326.                     DoKeyDown(key, window);
  4327.             break;
  4328.  
  4329.         case activateEvt:                        //true for activate, false for deactivate
  4330.             DoActivate((WindowPtr)event->message, event->modifiers & activeFlag);
  4331.             break;
  4332.  
  4333.         case updateEvt:                            //call DoUpdate with the window to update
  4334.             DoUpdate((WindowPtr)event->message);
  4335.             break;
  4336.  
  4337.         case diskEvt:                                //Call DIBadMount in response to a diskEvt
  4338.             if (HighWord(event->message) != noErr) {
  4339.                 where.v = kRecordTop;
  4340.                 where.h = kRecordLeft;
  4341.                 err = DIBadMount(where, event->message);
  4342.             }
  4343.             break;
  4344.  
  4345.         case osEvt:
  4346.             switch (event->message >> 24) {                    //get high byte of message
  4347.  
  4348.                 case suspendResumeMessage:
  4349.                     if ((event->message & suspendResumeMessage) != 0)
  4350.                         gInBackground = false;                //it was a resume event
  4351.                     else {
  4352.                         gInBackground = true;                //it was a suspend event
  4353.                         if (GetSoundMgrVersion() == 1)
  4354.                             KillSound();                    //stop any sound
  4355.                         window = FrontWindow();                //get front window
  4356.                         if (window != nil)                     //don’t use a nil window
  4357.                             HiliteWindow(window, false);    //then properly activate it
  4358.                     }
  4359.                     DoActivate(FrontWindow(), ! gInBackground);
  4360.                     break; //suspendResumeMessage
  4361.             }
  4362.             break; //osEvt
  4363.  
  4364.         case kHighLevelEvent:
  4365.             AEProcessAppleEvent(event);
  4366.             break;
  4367.  
  4368.     } //switch (event->what)
  4369. }
  4370.  
  4371. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4372. /*
  4373. Change the cursor’s shape, depending on its current position.  This also
  4374. calculates the region where the current cursor resides (for
  4375. WaitNextEvent).  This is based on its current position in global
  4376. coordinates.  If the mouse is ever outside of that region, an event is
  4377. generated causing this routine to be called again by the event loop.  This
  4378. allows me to change the region to where the mouse is currently located.
  4379. If there is more to the event than just “the mouse moved,” this gets
  4380. called before the event is processed to make sure the cursor is the right
  4381. one.  In any (ahem) event, this is called again before I fall back into
  4382. WaitNextEvent.  Extreme short values are used to create a wide open
  4383. region.  -SHRT_MAX - 1 is the largest negative short (-32768) and SHRT_MAX
  4384. is the largest positive short (32767).
  4385.  
  4386. BUG NOTE: The largest positive value for a region’s size is SHRT_MAX - 1
  4387. due to a very old bug that still remains to this day.
  4388. */
  4389.  
  4390. #pragma segment Main
  4391. void AdjustCursor(RgnHandle region)
  4392. {
  4393.     WindowPtr window;
  4394.     RgnHandle arrowRgn;
  4395.     RgnHandle sndCursorRgn;
  4396.     Rect sndCursorRect;
  4397.  
  4398.     window = FrontWindow();            //I only adjust the cursor when I am in front
  4399.     if ((! gInBackground) && (! IsDAWindow(window))) {
  4400.  
  4401.         arrowRgn = NewRgn();        //calculate regions for different cursor shapes
  4402.         sndCursorRgn = NewRgn();    //start with a big, big rectangular region
  4403.         SetRectRgn(arrowRgn, -SHRT_MAX - 1, -SHRT_MAX - 1, SHRT_MAX - 1, SHRT_MAX - 1);
  4404.  
  4405.         if (IsDocWindow(window)) {                //calculate region for document cursor
  4406.             sndCursorRect = window->portRect;
  4407.             SetPort(window);
  4408.  
  4409.             //make a global version of the viewRect
  4410.             LocalToGlobal(&TopLeft(sndCursorRect));
  4411.             LocalToGlobal(&BottomRight(sndCursorRect));
  4412.             RectRgn(sndCursorRgn, &sndCursorRect);
  4413.             SetOrigin(-window->portBits.bounds.left, -window->portBits.bounds.top);
  4414.             SectRgn(sndCursorRgn, window->visRgn, sndCursorRgn);
  4415.             SetOrigin(0, 0);
  4416.         }
  4417.  
  4418.         DiffRgn(arrowRgn, sndCursorRgn, arrowRgn);    //subtract other region
  4419.         if (PtInRgn(GetGlobalMouse(), sndCursorRgn)) {
  4420.             SetCursor(*(GetCursor(rSndCursor)));     //change cursor and region
  4421.             CopyRgn(sndCursorRgn, region);
  4422.         } else {
  4423.             SetCursor(&qd.arrow);
  4424.             CopyRgn(arrowRgn, region);
  4425.         }
  4426.         DisposeRgn(arrowRgn);                        //get rid of our local regions
  4427.         DisposeRgn(sndCursorRgn);
  4428.     }
  4429. }
  4430.  
  4431. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4432. /*
  4433. Get events forever, and handle them by calling DoEvent.  Since this
  4434. application requires System 6.0x or later I know that _WaitNextEvent is
  4435. always there, even without MultiFinder.  MultiFinder’s sleep is used to
  4436. determine how often I want to receive events, regardless if an event has
  4437. actually occurred.  In this application, I don’t perform any background
  4438. processing so I’m being super friendly by using LONG_MAX as a sleep
  4439. value.  This also helps keep Virtual Memory from paging me in just to run
  4440. my event loop and find out that nothing happened.  The application will
  4441. only be called upon for events that must be handled.  Also call
  4442. AdjustCursor each time through the loop.  AdjustCursor will return the
  4443. current region containing the mouse and is passed to WaitNextEvent.  If
  4444. the mouse travels out of the region, another event is generated.  I have
  4445. to call AdjustCursor just before doing the event to make sure the right
  4446. cursor is shown.  Another thing, if I have a sound playing asynchronously
  4447. then I want to dispose of my channel as soon as possible.  I set up a flag
  4448. in the SoundUnit that will keep track of when it has a channel allocated.
  4449. I can call the HasChannelOpen() function to find out if this is true.  It
  4450. pretty much like keeping track of when you’re in the background.  If a
  4451. channel is open, then the MultiFinder sleep time is adjusted to a
  4452. reasonable time that will allow me to catch when the sound has completed
  4453. so that I may dispose of my channels and status window.  That’s when I
  4454. return kPollingSleepTime.
  4455.  
  4456. VERSION 1.1: No longer putting the SANELib into a seperate segment, which then
  4457. needed to be unloaded.  Instead, I merge it into the Main segment.  Refer
  4458. to the Make file for further information.
  4459.  
  4460. VERSION 1.2: No longer using the SANELib at all. All of the SANE calls
  4461. are inline, and do not need a library to be linked in.
  4462. */
  4463.  
  4464. #pragma segment Main
  4465. void EventLoop(void)
  4466. {
  4467.     RgnHandle cursorRgn;
  4468.     EventRecord event;
  4469.     long sleep;
  4470.  
  4471.     cursorRgn = NewRgn();                    //1st time pass WNE an empty region
  4472.     while (!gTerminate) {
  4473.         if (HasChannelOpen())                //if we’re playing a sound
  4474.             sleep = kPollingSleepTime;        //use the polling sleep value
  4475.         else {
  4476.             sleep = LONG_MAX;                //default value for sleep
  4477.             UnloadSeg(_SoundUnit);            //unload the Sound Unit
  4478.         }
  4479.         UnloadSeg(OpenSoundDoc);            //unload the open code
  4480.         if (HasSoundCompleted())
  4481.             KillSound();
  4482.         if (LowOnReserve())
  4483.             RecoverReserve();
  4484.         AdjustCursor(cursorRgn);            //get the right cursor
  4485.         if (WaitNextEvent(everyEvent, &event, sleep, cursorRgn)) {
  4486.             AdjustCursor(cursorRgn);        //get the right cursor
  4487.             DoEvent(&event);
  4488.         }
  4489.     };                                        //loop forever; I quit through Terminate
  4490. }
  4491.  
  4492. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4493. /*
  4494. Set up the whole world.  Initialize global variables, Toolbox managers,
  4495. and menus.  If a failure occurs here, I will consider that the application
  4496. is in such bad shape that I should just exit.  Your error handling may
  4497. differ, but the checks should still be made.  I ask for a set of master
  4498. pointer blocks and all permanent storage at this point to cut down on
  4499. memory fragmentation.  I may be opening large numbers of resources and
  4500. documents so I allocate some extra master pointer blocks at the start.
  4501.  
  4502. VERSION 1.1:  Now supports opening of documents from the Finder.
  4503. */
  4504.  
  4505. #pragma segment Initialize
  4506. void Initialize(void)
  4507. {
  4508. #define kBroughtToFront    3
  4509.  
  4510.     EventRecord event;
  4511.     Handle menuBar;
  4512.     long response;
  4513.     short count;
  4514.     OSErr ignoreErr;
  4515.     Boolean ignoreResult;
  4516.  
  4517.     gTerminate = false;
  4518.     gInBackground = false;                        //we’ll be in the foreground soon
  4519.     gAppResRef = CurResFile();                    //save the resRef to myself
  4520.     for (count = 1; count <= kNumberOfMasters; count++)    //allocate master pointer blocks
  4521.         MoreMasters();
  4522.     InitGraf(&qd.thePort);                        //init managers, yawn...
  4523.     InitFonts();
  4524.     InitWindows();
  4525.     InitMenus();
  4526.     TEInit();
  4527.     InitDialogs(nil);
  4528.     InitCursor();
  4529.  
  4530. /*
  4531. ErrorSound is used to prevent the Dialog Manager from calling _SysBeep.
  4532. If I get a memory or resource Manager error it wouldn’t be the best
  4533. of plans to call _SysBeep which will want to allocate memory and load
  4534. a few resources.  Which could be bad if I just gave an Alert signalling
  4535. an out of memory condition. You only have to install this for Sound Manager
  4536. version  1 which is pretty buggy.
  4537.  
  4538. VERSION 1.2: Since we're using Sound Manager 2 or later, we don't have to
  4539. disable the multiple sampled sound channels demo and we don't need DoErrorSound.
  4540.  
  4541.     if (GetSoundMgrVersion() == 1)
  4542.     {
  4543.         ErrorSound(GetRoutineAddress(DoErrorSound));
  4544.         DisableItem(GetMenu(mDemos), iSampleSATB);
  4545.         DisableItem(GetMenu(mDemos), iSampleMelody);
  4546.     }
  4547. */
  4548.  
  4549. /*
  4550. This code is necessary to pull the application into the foreground.  I use
  4551. EventAvail because I don’t want to remove any events the user may have
  4552. done, such as typing ahead.  Until the application has made a few calls (3
  4553. seems to be the magic number) to the Event Manager, MultiFinder keeps me
  4554. in the background.   Splashscreens and Alerts will remain in a background
  4555. layer until we get a few events.  This is documented in Tech Note #180.
  4556. */
  4557.  
  4558.     for (count = 1; count <= kBroughtToFront; count++)
  4559.         ignoreResult = EventAvail(everyEvent, &event);
  4560.  
  4561. /*
  4562. Ignore the error returned from SysEnvirons; even if an error occurred,
  4563. the SysEnvirons glue will fill in the SysEnvRec. You can save a redundant
  4564. call to SysEnvirons by calling it after initializing AppleTalk.
  4565.  
  4566. VERSION 1.2: Using Gestalt and checking for System 7. Make sure you know if
  4567. the Gestalt trap is available before calling it. This code is compiled with the
  4568. SystemSevenOrLater flag on, when means you do not get the safe glue for Gestalt.
  4569. */
  4570.     if (! TrapExists(_Gestalt))
  4571.         EmergencyExit(sWrongVersion);
  4572.  
  4573.     if (Gestalt(gestaltSystemVersion, &response) != noErr)
  4574.         EmergencyExit(sStandardErr);
  4575.  
  4576.     if (response < 0x0700)
  4577.         EmergencyExit(sWrongVersion);
  4578.  
  4579. /*
  4580. Call the SoundUnit to initialize itslef.  If the SoundUnit encounters an error,
  4581. then it cannot be used and this means I’m leaving too.
  4582. */
  4583.  
  4584.     if (InitSoundUnit() != noErr)                //allocates 4 * 1064 bytes
  4585.         EmergencyExit(sInitSoundErr);
  4586.  
  4587. /*
  4588. Before I go any further, I want my reserve memory.  This is an emergency
  4589. reserve (sorta like my old VW had) when memory runs low.  If I cannot
  4590. obtain this reserve, then I’ll bail.  It’s also important to obtain my
  4591. reserve before testing if I have the desired amount of memory to run
  4592. this application.  Also, FailLowMemory will consider the memory reserve.
  4593. */
  4594.  
  4595.     if (! AllocateReserve())
  4596.         EmergencyExit(sLowMemory);
  4597.     SetGrowZone(GetRoutineAddress(MyGrowZone));
  4598.  
  4599.     menuBar = GetNewMBar(rMenuBar);                    //read menus into menu bar
  4600.     if (menuBar == nil)
  4601.         EmergencyExit(sNoMenus);                    //wow, how’d that happen?
  4602.     SetMenuBar(menuBar);                            //install menus
  4603.     DisposeHandle(menuBar);
  4604.     AppendResMenu(GetMenuHandle(mApple), 'DRVR');    //add DA names to Apple menu
  4605.     DrawMenuBar();
  4606.  
  4607.     InitStatusWindow();                                //get the status window ready
  4608.     InitCursorCtl(nil);                                //MPW’s handy cursor routines
  4609.  
  4610. /*
  4611. Last, I want to make sure that enough memory is free for my application to
  4612. run.  It is possible that user may have adjusted the SIZE resource to too
  4613. small a setting or for some other reason the application started up in a
  4614. very small memory partition.  It’s also possible for a situation to arise
  4615. where the heap may have been of the requested size taken from the SIZE
  4616. resource, but a large scrap was loaded which left too little memory.  I
  4617. want to make sure that my free memory is not being modified by the scrap’s
  4618. presence.  So, I unload it to disk but if the application will run once
  4619. the scrap is unloaded, then you’ll probably not get it back into memory.
  4620. Thus losing the clipboard contents.  I preform this check after
  4621. initializing all the Toolbox and the basic features of this application,
  4622. such as showing the about box.
  4623. */
  4624.     if (FailLowMemory(kMinSpace)) {
  4625.         if (UnloadScrap() != noErr)
  4626.             EmergencyExit(sLowMemory);
  4627.         else {
  4628.             if (FailLowMemory(kMinSpace))
  4629.                 EmergencyExit(sLowMemory);
  4630.         }
  4631.     }
  4632.  
  4633. /*
  4634. Install the four core AppleEvent handlers.
  4635. */
  4636.     ignoreErr = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
  4637.                 GetRoutineAddress(QuitApplicationEvent), 0, false);
  4638.  
  4639.     ignoreErr = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
  4640.                 GetRoutineAddress(OpenDocumentsEvent), 0, false);
  4641.  
  4642.     ignoreErr = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments,
  4643.                 GetRoutineAddress(PrintDocumentsEvent), 0, false);
  4644.  
  4645.     ignoreErr = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
  4646.                  GetRoutineAddress(OpenApplicationEvent), 0, false);
  4647. }
  4648.  
  4649. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4650. /*
  4651. This routine is contained in the MPW runtime library.  It will be placed
  4652. into the code segment used to initialize the A5 globals.  This external
  4653. reference to it is done so that we can unload that segment, named %A5Init.
  4654. */
  4655.  
  4656. #ifdef applec
  4657. extern void _DataInit(void);
  4658. #endif
  4659.  
  4660. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4661. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN PROGRAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4662. /*
  4663. If you have stack requirements that differ from the default, then you
  4664. could use SetApplLimit to increase StackSpace at this point, before
  4665. calling MaxApplZone.
  4666. */
  4667.  
  4668. #pragma segment Main
  4669. void main(void)
  4670. {
  4671. #ifdef applec
  4672.     UnloadSeg(_DataInit);            //note that _DataInit must not be in Main!
  4673. #endif
  4674.     MaxApplZone();                    //expand the heap so code segments load at the top
  4675.     Initialize();                    //initialize the program
  4676.     UnloadSeg(Initialize);            //note that Initialize must not be in Main!
  4677.     EventLoop();                    //call the main event loop
  4678.     ExitToShell();                    //we're out of here!
  4679. }
  4680. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN PROGRAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4681. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4682.